Skip to content

Commit 2e1e90f

Browse files
authored
Merge pull request #227 from wnlen/dev
fix(select): block manual node picking for auto strategy groups #214
2 parents 8a2d8cf + 2202bbf commit 2e1e90f

2 files changed

Lines changed: 287 additions & 20 deletions

File tree

scripts/core/clashctl.sh

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6191,7 +6191,7 @@ cmd_select() {
61916191

61926192
if [ -z "${1:-}" ]; then
61936193
print_select_context || return $?
6194-
proxy_select_interactive
6194+
proxy_select_interactive_guarded
61956195
return 0
61966196
fi
61976197

@@ -6217,7 +6217,7 @@ cmd_proxy_groups() {
62176217
[ -n "${group:-}" ] || continue
62186218
found="true"
62196219
type="$(proxy_group_type "$group" 2>/dev/null || echo "unknown")"
6220-
current="$(proxy_group_current "$group" 2>/dev/null || echo "-")"
6220+
current="$(proxy_group_current "$group" 2>/dev/null || true)"
62216221
printf ' 📦 %-20s %-12s %s\n' "$group" "$type" "$current"
62226222
done < <(proxy_group_list)
62236223

@@ -6333,14 +6333,14 @@ proxy_pick_index() {
63336333
}
63346334

63356335
proxy_pick_group_interactive() {
6336-
local idx count group current
6336+
local idx count group current type
63376337
local -a groups=()
63386338
local -a ordered_groups=()
63396339

63406340
while IFS= read -r group; do
63416341
[ -n "${group:-}" ] || continue
63426342
groups+=("$group")
6343-
done < <(proxy_group_manual_list)
6343+
done < <(proxy_group_display_list)
63446344

63456345
for group in "节点选择" "自动选择"; do
63466346
if printf '%s\n' "${groups[@]}" | grep -Fxq "$group"; then
@@ -6543,6 +6543,139 @@ proxy_select_interactive() {
65436543
print_select_feedback "$group"
65446544
}
65456545

6546+
proxy_pick_group_for_select() {
6547+
local idx count group current type
6548+
local -a groups=()
6549+
local -a ordered_groups=()
6550+
6551+
while IFS= read -r group; do
6552+
[ -n "${group:-}" ] || continue
6553+
groups+=("$group")
6554+
done < <(proxy_group_display_list)
6555+
6556+
for group in "节点选择" "自动选择"; do
6557+
if printf '%s\n' "${groups[@]}" | grep -Fxq "$group"; then
6558+
ordered_groups+=("$group")
6559+
fi
6560+
done
6561+
6562+
for group in "${groups[@]}"; do
6563+
case "$group" in
6564+
节点选择|自动选择)
6565+
continue
6566+
;;
6567+
esac
6568+
ordered_groups+=("$group")
6569+
done
6570+
6571+
groups=("${ordered_groups[@]}")
6572+
6573+
count="${#groups[@]}"
6574+
[ "$count" -gt 0 ] || die "📭 暂无可切换策略组"
6575+
6576+
echo "📦 请选择策略组:" >&2
6577+
echo "💡 通常优先选择:节点选择" >&2
6578+
idx=1
6579+
for group in "${groups[@]}"; do
6580+
current="$(proxy_group_current "$group" 2>/dev/null || true)"
6581+
type="$(proxy_group_type_label "$group" 2>/dev/null || echo "unknown")"
6582+
[ -n "${current:-}" ] || current="<unknown>"
6583+
printf ' %s) 📦 %s [%s] -> 🚀 %s\n' "$idx" "$group" "$type" "$current" >&2
6584+
idx=$((idx + 1))
6585+
done
6586+
echo " q) 退出" >&2
6587+
echo >&2
6588+
6589+
idx="$(proxy_pick_index "$count")" || return 1
6590+
echo "${groups[$((idx - 1))]}"
6591+
}
6592+
6593+
print_proxy_group_manual_pick_blocked() {
6594+
local group="$1"
6595+
local current type message
6596+
6597+
message="$(proxy_group_manual_pick_error_message "$group" 2>/dev/null || echo "该策略组不支持手动切换:$group")"
6598+
current="$(proxy_group_current "$group" 2>/dev/null || true)"
6599+
type="$(proxy_group_type_label "$group" 2>/dev/null || echo "unknown")"
6600+
6601+
ui_warn "$message"
6602+
ui_kv "📦" "策略组" "$group"
6603+
ui_kv "🧭" "策略组类型" "$type"
6604+
if [ -n "${current:-}" ]; then
6605+
ui_kv "🚀" "当前节点" "$current"
6606+
else
6607+
ui_kv "🚀" "当前节点" "<unknown>"
6608+
fi
6609+
ui_blank
6610+
}
6611+
6612+
proxy_select_interactive_guarded() {
6613+
local group="${1:-}"
6614+
local current idx count total_count node selected_node
6615+
local -a nodes=()
6616+
6617+
prepare
6618+
6619+
if ! status_is_running; then
6620+
die_state "代理内核未运行" "clashon"
6621+
fi
6622+
6623+
if ! proxy_controller_reachable 2>/dev/null; then
6624+
die_state "控制器不可访问" "clashctl doctor"
6625+
fi
6626+
6627+
if [ -z "${group:-}" ]; then
6628+
ui_title "🚀 节点切换"
6629+
group="$(proxy_pick_group_for_select)" || return 0
6630+
fi
6631+
6632+
proxy_group_exists "$group" || die "策略组不存在:$group"
6633+
6634+
if ! proxy_group_supports_manual_pick "$group"; then
6635+
print_proxy_group_manual_pick_blocked "$group"
6636+
return 0
6637+
fi
6638+
6639+
current="$(proxy_group_current "$group" 2>/dev/null || true)"
6640+
6641+
while IFS= read -r node; do
6642+
[ -n "${node:-}" ] || continue
6643+
nodes+=("$node")
6644+
done < <(proxy_group_selectable_nodes "$group")
6645+
6646+
count="${#nodes[@]}"
6647+
[ "$count" -gt 0 ] || die "📭 当前策略组没有候选节点:$group"
6648+
total_count="$(select_total_node_count 2>/dev/null || true)"
6649+
6650+
echo
6651+
echo "📦 当前策略组:$group"
6652+
[ -n "${current:-}" ] && echo "🚀 当前节点:$current"
6653+
echo "📦 当前策略组候选数:$count"
6654+
[ -n "${total_count:-}" ] && echo "🔢 全部节点总数:$total_count"
6655+
echo "ℹ️ 以下仅显示当前策略组可切换节点"
6656+
echo
6657+
echo "🚀 请选择节点:"
6658+
6659+
idx=1
6660+
for node in "${nodes[@]}"; do
6661+
if [ "$node" = "$current" ]; then
6662+
printf ' %s) 🚀 %s [当前]\n' "$idx" "$node"
6663+
else
6664+
printf ' %s) 🚀 %s\n' "$idx" "$node"
6665+
fi
6666+
idx=$((idx + 1))
6667+
done
6668+
6669+
echo " q) 退出"
6670+
echo
6671+
6672+
idx="$(proxy_pick_index "$count")" || return 0
6673+
selected_node="${nodes[$((idx - 1))]}"
6674+
6675+
proxy_group_select "$group" "$selected_node"
6676+
print_select_feedback "$group"
6677+
}
6678+
65466679
cmd_proxy() {
65476680
echo "⚠ 当前版本不提供 proxy 子命令"
65486681
echo "👉 使用 clashon / clashoff 控制代理"

scripts/core/proxy.sh

Lines changed: 150 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -278,13 +278,50 @@ proxy_group_type() {
278278
proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies.\"$group\".type // \"\"" - 2>/dev/null
279279
}
280280

281-
proxy_group_is_selector() {
281+
proxy_group_type_key() {
282282
local type
283283

284-
type="$(proxy_group_type "$1")"
284+
type="$(proxy_group_type "$1" 2>/dev/null || true)"
285+
printf '%s' "${type:-}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]_-'
286+
}
287+
288+
proxy_group_type_label() {
289+
local group="$1"
290+
local type_key raw_type
291+
292+
type_key="$(proxy_group_type_key "$group")"
293+
294+
case "$type_key" in
295+
selector)
296+
echo "select"
297+
;;
298+
urltest)
299+
echo "自动选择 / url-test"
300+
;;
301+
fallback)
302+
echo "故障转移 / fallback"
303+
;;
304+
loadbalance)
305+
echo "负载均衡 / load-balance"
306+
;;
307+
*)
308+
raw_type="$(proxy_group_type "$group" 2>/dev/null || true)"
309+
if [ -n "${raw_type:-}" ] && [ "$raw_type" != "null" ]; then
310+
echo "$raw_type"
311+
else
312+
echo "unknown"
313+
fi
314+
;;
315+
esac
316+
}
317+
318+
proxy_group_can_show_candidates() {
319+
local type_key
320+
321+
type_key="$(proxy_group_type_key "$1")"
285322

286-
case "$type" in
287-
Selector|URLTest|Fallback|LoadBalance)
323+
case "$type_key" in
324+
selector|urltest|fallback|loadbalance)
288325
return 0
289326
;;
290327
*)
@@ -293,22 +330,25 @@ proxy_group_is_selector() {
293330
esac
294331
}
295332

333+
proxy_group_is_selector() {
334+
proxy_group_can_show_candidates "$1"
335+
}
336+
296337
proxy_group_is_manual_selector() {
297338
proxy_group_supports_manual_pick "$1"
298339
}
299340

300341
proxy_group_is_auto_managed() {
301342
local group="$1"
302-
local type normalized_type
343+
local type_key
303344

304345
[ -n "${group:-}" ] || return 0
305346
proxy_group_exists "$group" || return 0
306347

307-
type="$(proxy_group_type "$group" 2>/dev/null || true)"
308-
normalized_type="$(printf '%s' "${type:-}" | tr '[:upper:]' '[:lower:]')"
348+
type_key="$(proxy_group_type_key "$group")"
309349

310-
case "$normalized_type" in
311-
urltest|url-test|fallback|loadbalance|load-balance)
350+
case "$type_key" in
351+
urltest|fallback|loadbalance)
312352
return 0
313353
;;
314354
esac
@@ -389,14 +429,54 @@ proxy_group_selectable_nodes() {
389429
done < <(proxy_group_nodes "$group")
390430
}
391431

392-
proxy_group_supports_manual_pick() {
432+
proxy_group_has_selectable_candidates() {
393433
local group="$1"
394434
local node
435+
436+
[ -n "${group:-}" ] || return 1
437+
proxy_group_exists "$group" || return 1
438+
proxy_group_can_show_candidates "$group" || return 1
439+
440+
while IFS= read -r node; do
441+
[ -n "${node:-}" ] || continue
442+
return 0
443+
done < <(proxy_group_selectable_nodes "$group")
444+
445+
return 1
446+
}
447+
448+
proxy_group_manual_pick_error_message() {
449+
local group="$1"
450+
local type_key type_label
451+
452+
[ -n "${group:-}" ] || {
453+
echo "该策略组不支持手动切换"
454+
return 0
455+
}
456+
457+
type_key="$(proxy_group_type_key "$group")"
458+
type_label="$(proxy_group_type_label "$group")"
459+
460+
case "$type_key" in
461+
urltest|fallback|loadbalance)
462+
echo "该策略组为${type_label}类型,不支持手动切换:$group"
463+
;;
464+
*)
465+
echo "该策略组不支持手动切换:$group"
466+
;;
467+
esac
468+
}
469+
470+
proxy_group_supports_manual_pick() {
471+
local group="$1"
395472
local has_now=""
473+
local type_key
396474

397475
[ -n "${group:-}" ] || return 1
398476
proxy_group_exists "$group" || return 1
399-
proxy_group_is_auto_managed "$group" && return 1
477+
478+
type_key="$(proxy_group_type_key "$group")"
479+
[ "$type_key" = "selector" ] || return 1
400480

401481
has_now="$(
402482
proxy_groups_json \
@@ -405,12 +485,17 @@ proxy_group_supports_manual_pick() {
405485
)"
406486
[ "${has_now:-false}" = "true" ] || return 1
407487

408-
while IFS= read -r node; do
409-
[ -n "${node:-}" ] || continue
410-
return 0
411-
done < <(proxy_group_selectable_nodes "$group")
488+
proxy_group_has_selectable_candidates "$group"
489+
}
412490

413-
return 1
491+
proxy_group_display_list() {
492+
local group
493+
494+
while IFS= read -r group; do
495+
[ -n "${group:-}" ] || continue
496+
proxy_group_has_selectable_candidates "$group" || continue
497+
echo "$group"
498+
done < <(proxy_group_list)
414499
}
415500

416501
proxy_group_manual_list() {
@@ -472,6 +557,55 @@ proxy_group_select() {
472557
rm -f "$response_file" 2>/dev/null || true
473558
}
474559

560+
proxy_group_select() {
561+
local group="$1"
562+
local node="$2"
563+
local base secret
564+
local code response_file response_body
565+
local available_node found
566+
567+
[ -n "${group:-}" ] || die "策略组名称不能为空"
568+
[ -n "${node:-}" ] || die "节点名称不能为空"
569+
570+
proxy_group_exists "$group" || die "策略组不存在:$group"
571+
proxy_group_supports_manual_pick "$group" || die "$(proxy_group_manual_pick_error_message "$group")"
572+
573+
found=false
574+
while IFS= read -r available_node; do
575+
[ -n "${available_node:-}" ] || continue
576+
if [ "$available_node" = "$node" ]; then
577+
found=true
578+
break
579+
fi
580+
done < <(proxy_group_selectable_nodes "$group")
581+
582+
if [ "$found" != "true" ]; then
583+
die "节点不存在于策略组中:$group -> $node"
584+
fi
585+
586+
base="$(controller_api_base)"
587+
secret="$(controller_secret)"
588+
response_file="$(mktemp)"
589+
code="$(
590+
curl -sS -o "$response_file" -w "%{http_code}" -X PUT \
591+
-H "Content-Type: application/json" \
592+
${secret:+-H "Authorization: Bearer $secret"} \
593+
--data "{\"name\":\"$node\"}" \
594+
"$base/proxies/$group"
595+
)"
596+
597+
if [ "${code:-000}" -lt 200 ] || [ "${code:-000}" -ge 300 ]; then
598+
response_body="$(cat "$response_file" 2>/dev/null || true)"
599+
rm -f "$response_file" 2>/dev/null || true
600+
if [ -n "${response_body:-}" ]; then
601+
die "节点切换失败:$response_body"
602+
fi
603+
die "节点切换失败:controller 返回 HTTP $code"
604+
fi
605+
606+
rm -f "$response_file" 2>/dev/null || true
607+
}
608+
475609
proxy_group_count() {
476610
proxy_group_list 2>/dev/null | awk 'NF{c++} END{print c+0}'
477611
}

0 commit comments

Comments
 (0)