Skip to content

Instantly share code, notes, and snippets.

@growvv
Created May 20, 2026 07:11
Show Gist options
  • Select an option

  • Save growvv/0c5e62750a31a0da8e54aaf3c1059ebf to your computer and use it in GitHub Desktop.

Select an option

Save growvv/0c5e62750a31a0da8e54aaf3c1059ebf to your computer and use it in GitHub Desktop.
服务端管理Clash的TUI
#!/usr/bin/env bash
set -euo pipefail
API="${API:-http://127.0.0.1:9092}"
GROUP="${GROUP:-三毛机场}"
TEST_URL="${TEST_URL:-http://www.apple.com/library/test/success.html}"
TIMEOUT_MS="${TIMEOUT_MS:-5000}"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
GROUP_ENC="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$GROUP")"
need() {
command -v "$1" >/dev/null 2>&1 || {
echo "Missing required command: $1" >&2
exit 1
}
}
need curl
need python3
need whiptail
api_get() {
curl -fsS --connect-timeout 5 "$API$1"
}
api_put_json() {
curl -fsS --connect-timeout 5 -X PUT -H 'Content-Type: application/json' --data-binary @- "$API$1"
}
message() {
whiptail --title "Clash TUI" --msgbox "$1" 18 78
}
current_node() {
api_get "/proxies/$GROUP_ENC" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("now", ""))'
}
group_json() {
api_get "/proxies/$GROUP_ENC"
}
delay_json() {
api_get "/group/$GROUP_ENC/delay?timeout=$TIMEOUT_MS&url=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$TEST_URL
")"
}
show_current() {
local now
now="$(current_node)"
message "当前策略组: $GROUP
当前节点: $now
代理地址:
http_proxy=http://127.0.0.1:7892
https_proxy=http://127.0.0.1:7892"
}
show_nodes() {
local tmp
tmp="$(mktemp)"
trap 'rm -f "$tmp"' RETURN
group_json | python3 -c '
import sys, json
d = json.load(sys.stdin)
now = d.get("now", "")
for i, name in enumerate(d.get("all", []), 1):
mark = "*" if name == now else " "
print(f"{mark} {i:02d}. {name}")
' > "$tmp"
whiptail --title "所有节点 (* 为当前)" --textbox "$tmp" 30 100
}
test_nodes() {
local tmp
tmp="$(mktemp)"
trap 'rm -f "$tmp"' RETURN
{
echo "正在测试节点延迟,timeout=${TIMEOUT_MS}ms ..."
echo
delay_json | python3 -c '
import sys, json
d = json.load(sys.stdin)
for name, delay in sorted(d.items(), key=lambda item: item[1]):
print(f"{delay:>5} ms {name}")
'
} > "$tmp"
whiptail --title "节点延迟" --textbox "$tmp" 30 100
}
choose_node() {
local delays now menu tmp group_tmp choice
tmp="$(mktemp)"
group_tmp="$(mktemp)"
trap 'rm -f "$tmp" "$group_tmp"' RETURN
now="$(current_node)"
if whiptail --title "测试延迟" --yesno "是否先测试所有节点延迟并按延迟排序?" 10 60; then
delays="$(delay_json)"
else
delays='{}'
fi
group_json > "$group_tmp"
python3 - "$now" "$delays" "$group_tmp" > "$tmp" <<'PY'
import sys, json
now = sys.argv[1]
delays = json.loads(sys.argv[2])
with open(sys.argv[3], encoding="utf-8") as f:
d = json.load(f)
def is_info(name):
markers = ("剩余流量", "距离下次重置", "套餐到期", "官网更新软件", "每天更新一次订阅")
return any(m in name for m in markers)
names = d.get("all", [])
names.sort(key=lambda n: (is_info(n), delays.get(n, 10**9), n))
for name in names:
delay = delays.get(name)
prefix = "* " if name == now else " "
suffix = "info" if is_info(name) else ("--" if delay is None else f"{delay} ms")
print(name)
print(f"{prefix}{suffix}")
PY
mapfile -t menu < "$tmp"
choice="$(whiptail --title "选择节点" --menu "当前节点: $now" 30 100 20 "${menu[@]}" 3>&1 1>&2 2>&3)" || return 0
python3 - "$choice" <<'PY' | api_put_json "/proxies/$GROUP_ENC" >/dev/null
import sys, json
print(json.dumps({"name": sys.argv[1]}, ensure_ascii=False))
PY
message "已切换到:
$choice"
}
update_subscription() {
if ! whiptail --title "更新订阅" --yesno "将执行 update-subscription.sh 并重启 mihomo 服务,继续?" 10 70; then
return 0
fi
local tmp
tmp="$(mktemp)"
trap 'rm -f "$tmp"' RETURN
if "$ROOT_DIR/update-subscription.sh" >"$tmp" 2>&1 && systemctl --user restart mihomo >>"$tmp" 2>&1; then
echo >>"$tmp"
echo "Done." >>"$tmp"
else
echo >>"$tmp"
echo "Failed." >>"$tmp"
fi
whiptail --title "更新订阅结果" --textbox "$tmp" 20 90
}
main() {
api_get /version >/dev/null || {
echo "Cannot connect to Mihomo API: $API" >&2
echo "Try: systemctl --user start mihomo" >&2
exit 1
}
while true; do
local action
action="$(whiptail --title "Clash/Mihomo TUI" --menu "策略组: $GROUP" 18 72 8 \
current "查看当前节点" \
list "查看所有节点" \
test "测试所有节点延迟" \
choose "手动选择节点" \
update "更新订阅并重启" \
quit "退出" \
3>&1 1>&2 2>&3)" || break
case "$action" in
current) show_current ;;
list) show_nodes ;;
test) test_nodes ;;
choose) choose_node ;;
update) update_subscription ;;
quit) break ;;
esac
done
}
main "$@"
@growvv

growvv commented May 20, 2026

Copy link
Copy Markdown
Author
481317562ba775b6052a4712a4c1e851

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment