refactor(migration): auto-clean legacy subscription and env compat fields

This commit is contained in:
Arvin
2026-04-09 10:01:51 +08:00
parent bc97b0a2cd
commit 2a5cd3da8a
3 changed files with 225 additions and 197 deletions
+73 -30
View File
@@ -100,7 +100,7 @@ usage_advanced() {
💡 Notes:
当前编译链固定为 active-only
只处理当前主订阅,不再提供 merge / policy / select 编译集合
只处理当前 active 主订阅
Tun 模式属于高级能力,开启前建议先执行:clashctl tun doctor
EOF
}
@@ -340,27 +340,33 @@ cmd_ui() {
esac
}
status_build_mode() {
read_build_value "BUILD_MODE" 2>/dev/null || true
}
status_build_active_source() {
read_build_value "BUILD_ACTIVE_SOURCE" 2>/dev/null || true
}
status_build_selected_sources() {
read_build_value "BUILD_SELECTED_SOURCES" 2>/dev/null || true
}
status_build_active_sources() {
local value
status_build_included_sources() {
value="$(read_build_value "BUILD_ACTIVE_SOURCES" 2>/dev/null || true)"
if [ -n "${value:-}" ]; then
echo "$value"
return 0
fi
# 兼容历史 build.env
read_build_value "BUILD_INCLUDED_SOURCES" 2>/dev/null || true
}
status_build_min_success_sources() {
read_build_value "BUILD_MIN_SUCCESS_SOURCES" 2>/dev/null || true
}
status_build_failed_active_sources() {
local value
status_build_failed_sources() {
value="$(read_build_value "BUILD_FAILED_ACTIVE_SOURCES" 2>/dev/null || true)"
if [ -n "${value:-}" ]; then
echo "$value"
return 0
fi
# 兼容历史 build.env
read_build_value "BUILD_FAILED_SOURCES" 2>/dev/null || true
}
@@ -1727,7 +1733,7 @@ print_status_summary_compact() {
print_status_summary_verbose() {
local running_text profile mixed_port controller
local build_mode current_active build_included build_failed build_status build_time
local current_active build_active_sources build_failed_active_sources build_status build_time
local build_block_reason build_block_time
local last_switch_from last_switch_to last_switch_time
local controller_ok current_proxy_lines
@@ -1744,10 +1750,9 @@ print_status_summary_verbose() {
controller="$(status_read_controller 2>/dev/null || true)"
build_mode="$(status_build_mode)"
current_active="$(active_subscription_name 2>/dev/null || true)"
build_included="$(status_build_included_sources)"
build_failed="$(status_build_failed_sources)"
build_active_sources="$(status_build_active_sources)"
build_failed_active_sources="$(status_build_failed_active_sources)"
build_status="$(status_build_last_status)"
build_time="$(status_build_last_time)"
@@ -1876,8 +1881,8 @@ print_status_summary_verbose() {
echo "🧩 最近编译:未知"
fi
[ -n "${build_included:-}" ] && echo "🟢 实际参与编译:$build_included"
[ -n "${build_failed:-}" ] && echo "❌ 编译失败源:$build_failed"
[ -n "${build_active_sources:-}" ] && echo "🟢 实际参与编译:$build_active_sources"
[ -n "${build_failed_active_sources:-}" ] && echo "❌ 编译失败源:$build_failed_active_sources"
echo
echo "【当前订阅健康】"
@@ -2109,6 +2114,16 @@ doctor_fail() {
echo " ✘ $1"
}
doctor_yq_available() {
local yq_path
yq_path="$(yq_bin 2>/dev/null || true)"
[ -n "${yq_path:-}" ] && [ -x "$yq_path" ]
}
doctor_warn_skip_yq_parse() {
doctor_warn "缺少 yq,跳过配置解析类检查"
}
doctor_install_context() {
doctor_print_title "安装环境检查"
@@ -2254,6 +2269,16 @@ doctor_config() {
[ -n "${active_profile:-}" ] || active_profile="default"
doctor_ok "当前 Profile:$active_profile"
if ! doctor_yq_available; then
doctor_warn_skip_yq_parse
if test_runtime_config "$config_file" >/dev/null 2>&1; then
doctor_ok "配置校验通过"
else
doctor_fail "配置校验失败"
fi
return 0
fi
mixed_port="$("$(yq_bin)" eval '.["mixed-port"] // .port // ""' "$config_file" 2>/dev/null | head -n 1)"
controller="$("$(yq_bin)" eval '.["external-controller"] // ""' "$config_file" 2>/dev/null | head -n 1)"
@@ -2287,6 +2312,13 @@ doctor_subscription() {
return 0
fi
doctor_ok "订阅策略:active-only"
if ! doctor_yq_available; then
doctor_warn_skip_yq_parse
return 0
fi
active="$(active_subscription_name 2>/dev/null || true)"
total="$("$(yq_bin)" eval '.sources | keys | length' "$file" 2>/dev/null || echo 0)"
@@ -2303,7 +2335,6 @@ doctor_subscription() {
)"
auto_disabled_count="$(printf '%s\n' "${auto_disabled_count:-}" | awk 'NF{c++} END{print c+0}')"
doctor_ok "订阅策略:active-only"
[ -n "${active:-}" ] && doctor_ok "当前主订阅:$active" || doctor_warn "当前主订阅为空"
doctor_ok "订阅源总数:$total"
@@ -2325,15 +2356,15 @@ doctor_subscription() {
}
doctor_build() {
local active included failed last_status last_time
local active active_sources failed_active_sources last_status last_time
local block_reason block_time
local error_summary error_detail error_stage
doctor_print_title "编译状态检查"
active="$(read_build_value "BUILD_ACTIVE_SOURCE" 2>/dev/null || true)"
included="$(read_build_value "BUILD_INCLUDED_SOURCES" 2>/dev/null || true)"
failed="$(read_build_value "BUILD_FAILED_SOURCES" 2>/dev/null || true)"
active_sources="$(status_build_active_sources 2>/dev/null || true)"
failed_active_sources="$(status_build_failed_active_sources 2>/dev/null || true)"
last_status="$(read_build_value "BUILD_LAST_STATUS" 2>/dev/null || true)"
last_time="$(read_build_value "BUILD_LAST_TIME" 2>/dev/null || true)"
error_summary="$(read_build_value "BUILD_LAST_ERROR_SUMMARY" 2>/dev/null || true)"
@@ -2342,7 +2373,7 @@ doctor_build() {
block_reason="$(read_runtime_event_value "RUNTIME_LAST_BUILD_BLOCK_REASON" 2>/dev/null || true)"
block_time="$(read_runtime_event_value "RUNTIME_LAST_BUILD_BLOCK_TIME" 2>/dev/null || true)"
if [ -z "${active:-}${included:-}${failed:-}${last_status:-}${last_time:-}${error_summary:-}${error_stage:-}${block_reason:-}" ]; then
if [ -z "${active:-}${active_sources:-}${failed_active_sources:-}${last_status:-}${last_time:-}${error_summary:-}${error_stage:-}${block_reason:-}" ]; then
doctor_warn "未找到编译元数据(build.env)"
return 0
fi
@@ -2355,14 +2386,14 @@ doctor_build() {
doctor_warn "当前主订阅为空"
fi
if [ -n "${included:-}" ]; then
doctor_ok "实际参与编译:$included"
if [ -n "${active_sources:-}" ]; then
doctor_ok "实际参与编译:$active_sources"
else
doctor_warn "没有任何订阅参与编译"
fi
if [ -n "${failed:-}" ]; then
doctor_warn "失败订阅源:$failed"
if [ -n "${failed_active_sources:-}" ]; then
doctor_warn "失败订阅源:$failed_active_sources"
else
doctor_ok "失败订阅源:无"
fi
@@ -2462,6 +2493,18 @@ doctor_ports() {
return 0
fi
if ! doctor_yq_available; then
doctor_warn_skip_yq_parse
if [ -x "$(subconverter_bin)" ]; then
if is_port_in_use "$(subconverter_port)"; then
doctor_ok "subconverter 端口已监听:$(subconverter_port)"
else
doctor_warn "subconverter 端口未监听:$(subconverter_port)"
fi
fi
return 0
fi
mixed_port="$("$(yq_bin)" eval '.["mixed-port"] // .port // ""' "$config_file" 2>/dev/null | head -n 1)"
controller="$("$(yq_bin)" eval '.["external-controller"] // ""' "$config_file" 2>/dev/null | head -n 1)"
controller_port="${controller##*:}"
@@ -2798,7 +2841,7 @@ cmd_config() {
echo
echo "🧩 说明:"
echo " 当前编译链固定为 active-only"
echo " 只处理当前主订阅,不再提供 merge / policy / select 编译集合"
echo " 只处理当前 active 主订阅"
echo
ui_next "clashctl config show"
ui_blank
@@ -4682,4 +4725,4 @@ case "$cmd" in
esac
;;
*) die "未知命令:$cmd" ;;
esac
esac
+72 -23
View File
@@ -136,7 +136,7 @@ install_arch_text() {
}
install_state_text() {
local has_subscription install_ready runtime_ready controller_ready
local has_subscription install_ready runtime_ready controller_ready build_status
if install_has_subscription; then
has_subscription="true"
@@ -147,35 +147,43 @@ install_state_text() {
install_ready="$(read_runtime_event_value "RUNTIME_LAST_INSTALL_READY" 2>/dev/null || true)"
runtime_ready="$(install_verify_runtime_ready 2>/dev/null || true)"
controller_ready="$(install_verify_controller_ready 2>/dev/null || true)"
if [ "${install_ready:-false}" = "true" ]; then
echo "ready"
return 0
fi
build_status="$(read_build_value "BUILD_LAST_STATUS" 2>/dev/null || true)"
if [ "${has_subscription:-false}" != "true" ]; then
echo "stopped"
return 0
fi
if [ "${runtime_ready:-false}" = "true" ] || [ "${controller_ready:-false}" = "true" ]; then
echo "degraded"
if [ "${build_status:-}" = "failed" ]; then
echo "broken"
return 0
fi
echo "degraded"
if [ "${install_ready:-false}" = "true" ] \
|| { [ "${runtime_ready:-false}" = "true" ] && [ "${controller_ready:-false}" = "true" ]; }; then
echo "ready"
return 0
fi
echo "verifying"
}
install_build_result_text() {
local command_ready config_ready
local command_ready config_ready build_status
command_ready="$(install_verify_command_ready 2>/dev/null || true)"
config_ready="$(install_verify_config_ready 2>/dev/null || true)"
build_status="$(read_build_value "BUILD_LAST_STATUS" 2>/dev/null || true)"
if [ "${command_ready:-false}" = "true" ] && [ "${config_ready:-false}" = "true" ]; then
if [ "${build_status:-}" = "failed" ]; then
echo "failed"
return 0
fi
if [ "${command_ready:-false}" = "true" ] && { [ "${config_ready:-false}" = "true" ] || ! install_has_subscription; }; then
echo "success"
else
echo "failed"
echo "pending"
fi
}
@@ -184,7 +192,14 @@ install_next_step_text() {
ready)
echo "clashctl select"
;;
degraded)
verifying)
if install_has_subscription; then
echo "clashon"
else
echo "clashctl add <订阅链接>"
fi
;;
broken)
echo "clashctl doctor"
;;
stopped)
@@ -219,6 +234,9 @@ init_project_context() {
}
load_env_if_exists() {
local env_file
env_file="$PROJECT_DIR/.env"
if [ -f "$PROJECT_DIR/.env" ]; then
set -a
# shellcheck disable=SC1090
@@ -227,6 +245,7 @@ load_env_if_exists() {
fi
normalize_env_compat
cleanup_env_legacy_compat_fields "$env_file"
}
normalize_env_compat() {
@@ -262,9 +281,24 @@ normalize_env_compat() {
CLASH_SUBSCRIPTION_UA="$CLASH_SUB_UA"
fi
# 已废弃:active-only 主链不再消费该字段
unset BUILD_MIN_SUCCESS_SOURCES 2>/dev/null || true
return 0
}
cleanup_env_legacy_compat_fields() {
local file="$1"
[ -n "${file:-}" ] || return 0
[ -f "$file" ] || return 0
awk '
$0 ~ /^[[:space:]]*(export[[:space:]]+)?BUILD_MIN_SUCCESS_SOURCES=/ { next }
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}
github_proxy_prefix() {
local prefix="${CLASH_GH_PROXY:-}"
prefix="${prefix%/}"
@@ -2175,7 +2209,7 @@ remove_alias_command_wrappers() {
}
install_status_text() {
local has_subscription install_ready runtime_ready controller_ready
local has_subscription install_ready runtime_ready controller_ready build_status
local live_runtime="false"
local live_controller="false"
@@ -2188,6 +2222,7 @@ install_status_text() {
install_ready="$(read_runtime_event_value "RUNTIME_LAST_INSTALL_READY" 2>/dev/null || true)"
runtime_ready="$(install_verify_runtime_ready 2>/dev/null || true)"
controller_ready="$(install_verify_controller_ready 2>/dev/null || true)"
build_status="$(read_build_value "BUILD_LAST_STATUS" 2>/dev/null || true)"
if status_is_running 2>/dev/null; then
live_runtime="true"
@@ -2202,6 +2237,11 @@ install_status_text() {
return 0
fi
if [ "${build_status:-}" = "failed" ]; then
echo "broken"
return 0
fi
if [ "${live_runtime:-false}" = "true" ] && [ "${live_controller:-false}" = "true" ]; then
echo "ready"
return 0
@@ -2216,18 +2256,18 @@ install_status_text() {
if [ "${live_runtime:-false}" = "true" ] \
|| [ "${runtime_ready:-false}" = "true" ] \
|| [ "${controller_ready:-false}" = "true" ]; then
echo "degraded"
echo "verifying"
return 0
fi
echo "degraded"
echo "verifying"
}
install_status_label() {
case "$(install_status_text)" in
ready) echo "ready" ;;
stopped) echo "stopped" ;;
degraded) echo "degraded" ;;
verifying) echo "verifying" ;;
broken) echo "broken" ;;
*) echo "unknown" ;;
esac
@@ -2245,7 +2285,14 @@ install_default_next_action() {
echo "clashctl add <订阅链接>"
fi
;;
degraded|broken)
verifying)
if status_is_running 2>/dev/null; then
echo "clashctl status"
else
echo "clashon"
fi
;;
broken)
if install_has_subscription; then
echo "clashctl doctor"
else
@@ -2277,8 +2324,9 @@ install_runtime_brief_line() {
stopped)
echo "🔴 当前状态:stopped"
;;
degraded)
echo "🟡 当前状态:degraded"
verifying)
echo "🟡 当前状态:verifying"
echo "🧭 正在确认运行状态,可继续观察或手动启动"
if [ -n "${mixed_port:-}" ]; then
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
fi
@@ -2332,8 +2380,9 @@ print_install_summary() {
stopped)
echo "🚦 当前状态:⚪ stopped"
;;
degraded)
echo "🚦 当前状态:🟡 degraded"
verifying)
echo "🚦 当前状态:🟡 verifying"
echo "🧭 安装已完成,运行状态仍在确认中"
if [ -n "${mixed_port:-}" ]; then
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
fi
@@ -2351,4 +2400,4 @@ print_install_summary() {
echo "👉 下一步:${next_action}"
echo
}
}
+80 -144
View File
@@ -60,18 +60,39 @@ subscriptions_file() {
echo "$CONFIG_DIR/subscriptions.yaml"
}
migrate_subscriptions_legacy_fields() {
local file="$1"
[ -f "$file" ] || return 0
if [ -x "$(yq_bin 2>/dev/null || true)" ]; then
if "$(yq_bin)" eval '(.mode != null) or (.selected != null) or (.policy != null)' "$file" 2>/dev/null | grep -qx 'true'; then
"$(yq_bin)" eval -i 'del(.mode) | del(.selected) | del(.policy)' "$file" 2>/dev/null || true
fi
return 0
fi
# 无 yq 时做最小兜底:仅移除顶层旧字段,避免旧语义继续扩散
awk '
$0 ~ /^[[:space:]]*mode:[[:space:]]*/ { next }
$0 ~ /^[[:space:]]*selected:[[:space:]]*/ { next }
$0 ~ /^[[:space:]]*policy:[[:space:]]*/ { next }
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
}
ensure_subscriptions_file() {
local file
file="$(subscriptions_file)"
[ -f "$file" ] && return 0
if [ -f "$file" ]; then
migrate_subscriptions_legacy_fields "$file"
return 0
fi
mkdir -p "$CONFIG_DIR"
cat > "$file" <<'EOF'
mode: single
active: default
selected: []
sources:
default:
@@ -79,10 +100,8 @@ sources:
url: ""
enabled: true
EOF
}
build_min_success_sources() {
echo "1"
migrate_subscriptions_legacy_fields "$file"
}
csv_count() {
@@ -490,19 +509,15 @@ record_build_failure() {
local selected="$4"
local included="$5"
local failed="$6"
local min_required
min_required="$(build_min_success_sources)"
write_build_value "BUILD_MODE" "$mode"
write_build_value "BUILD_POLICY" "$policy"
# active-only 元数据:只记录当前实际参与编译的 active 订阅集合
# 为避免旧状态文件混淆,同时清空旧 key
write_build_value "BUILD_ACTIVE_SOURCE" "$active"
write_build_value "BUILD_SELECTED_SOURCES" "$selected"
write_build_value "BUILD_INCLUDED_SOURCES" "$included"
write_build_value "BUILD_FAILED_SOURCES" "$failed"
write_build_value "BUILD_SOURCE_COUNT" "$(csv_count "$selected")"
write_build_value "BUILD_INCLUDED_COUNT" "$(csv_count "$included")"
write_build_value "BUILD_MIN_SUCCESS_SOURCES" "$min_required"
write_build_value "BUILD_ACTIVE_SOURCES" "$included"
write_build_value "BUILD_FAILED_ACTIVE_SOURCES" "$failed"
write_build_value "BUILD_SELECTED_SOURCES" ""
write_build_value "BUILD_INCLUDED_SOURCES" ""
write_build_value "BUILD_FAILED_SOURCES" ""
write_build_value "BUILD_LAST_STATUS" "failed"
write_build_value "BUILD_LAST_TIME" "$(now_datetime)"
}
@@ -514,19 +529,13 @@ record_build_success() {
local selected="$4"
local included="$5"
local failed="$6"
local min_required
min_required="$(build_min_success_sources)"
write_build_value "BUILD_MODE" "$mode"
write_build_value "BUILD_POLICY" "$policy"
write_build_value "BUILD_ACTIVE_SOURCE" "$active"
write_build_value "BUILD_SELECTED_SOURCES" "$selected"
write_build_value "BUILD_INCLUDED_SOURCES" "$included"
write_build_value "BUILD_FAILED_SOURCES" "$failed"
write_build_value "BUILD_SOURCE_COUNT" "$(csv_count "$selected")"
write_build_value "BUILD_INCLUDED_COUNT" "$(csv_count "$included")"
write_build_value "BUILD_MIN_SUCCESS_SOURCES" "$min_required"
write_build_value "BUILD_ACTIVE_SOURCES" "$included"
write_build_value "BUILD_FAILED_ACTIVE_SOURCES" "$failed"
write_build_value "BUILD_SELECTED_SOURCES" ""
write_build_value "BUILD_INCLUDED_SOURCES" ""
write_build_value "BUILD_FAILED_SOURCES" ""
write_build_value "BUILD_LAST_STATUS" "success"
write_build_value "BUILD_LAST_TIME" "$(now_datetime)"
clear_build_error_detail
@@ -937,7 +946,7 @@ print_build_explain() {
error_detail="$(status_build_last_error_detail 2>/dev/null || true)"
ui_title "🧩 编译说明"
echo "- 当前编译链只处理 active 主订阅,不再 merge 多订阅"
echo "- 当前编译链只处理 active 主订阅"
echo "- 当前主订阅:${active:-未设置}"
case "${last_status:-unknown}" in
@@ -962,16 +971,30 @@ print_build_explain() {
ui_blank
}
build_selected_sources_csv() {
read_build_value "BUILD_SELECTED_SOURCES" 2>/dev/null || true
build_active_sources_csv() {
local value
value="$(read_build_value "BUILD_ACTIVE_SOURCES" 2>/dev/null || true)"
if [ -n "${value:-}" ]; then
echo "$value"
return 0
fi
# 兼容历史 build.env
value="$(read_build_value "BUILD_INCLUDED_SOURCES" 2>/dev/null || true)"
[ -n "${value:-}" ] && echo "$value"
}
build_included_sources_csv() {
read_build_value "BUILD_INCLUDED_SOURCES" 2>/dev/null || true
}
build_failed_active_sources_csv() {
local value
value="$(read_build_value "BUILD_FAILED_ACTIVE_SOURCES" 2>/dev/null || true)"
if [ -n "${value:-}" ]; then
echo "$value"
return 0
fi
build_failed_sources_csv() {
read_build_value "BUILD_FAILED_SOURCES" 2>/dev/null || true
# 兼容历史 build.env
value="$(read_build_value "BUILD_FAILED_SOURCES" 2>/dev/null || true)"
[ -n "${value:-}" ] && echo "$value"
}
build_last_status() {
@@ -995,66 +1018,36 @@ build_last_error_detail() {
}
build_success_explanation_lines() {
local mode policy included_csv failed_csv included_count min_required
local included_csv included_count
mode="$(subscription_mode 2>/dev/null || true)"
policy="$(subscription_policy 2>/dev/null || true)"
included_csv="$(build_included_sources_csv)"
failed_csv="$(build_failed_sources_csv)"
included_csv="$(build_active_sources_csv)"
included_count="$(csv_count "$included_csv")"
min_required="$(build_min_success_sources)"
case "$mode" in
single)
echo "- single 模式下仅当前主订阅参与编译"
;;
esac
echo "- 实际成功源数量 ${included_count},达到最少成功源要求 ${min_required}"
echo "- active-only 编译链仅处理当前主订阅"
echo "- 当前成功源数量:${included_count}"
}
build_failure_explanation_lines() {
local mode policy included_csv failed_csv included_count min_required error_stage
local included_csv included_count error_stage
mode="$(subscription_mode 2>/dev/null || true)"
policy="$(subscription_policy 2>/dev/null || true)"
included_csv="$(build_included_sources_csv)"
failed_csv="$(build_failed_sources_csv)"
included_csv="$(build_active_sources_csv)"
failed_csv="$(build_failed_active_sources_csv)"
included_count="$(csv_count "$included_csv")"
min_required="$(build_min_success_sources)"
error_stage="$(build_last_error_stage)"
case "$mode" in
single)
echo "- single 模式下仅当前主订阅参与编译"
;;
merge)
echo "- merge 模式下 selected 集合参与编译"
;;
esac
case "$policy" in
strict)
if [ -n "${failed_csv:-}" ]; then
echo "- strict 策略下任一订阅失败都会导致整体失败"
fi
;;
tolerant)
if [ "$error_stage" = "min-success-threshold" ]; then
echo "- tolerant 虽允许跳过失败源,但仍必须满足最少成功源要求"
elif [ -n "${failed_csv:-}" ]; then
echo "- tolerant 已跳过失败源,但后续校验仍未通过"
fi
;;
esac
echo "- 实际成功源数量 ${included_count},最少成功源要求 ${min_required}"
echo "- active-only 编译链仅处理当前主订阅"
if [ "$error_stage" = "resolve-active-source" ]; then
echo "- 当前未找到可用的 active 主订阅"
elif [ -n "${failed_csv:-}" ]; then
echo "- 当前 active 主订阅拉取或校验失败"
fi
echo "- 当前成功源数量:${included_count}"
}
print_build_failed_source_lines() {
local failed_csv name last_error
failed_csv="$(build_failed_sources_csv)"
failed_csv="$(build_failed_active_sources_csv)"
[ -n "${failed_csv:-}" ] || return 0
IFS=',' read -r -a _failed_names <<< "$failed_csv"
@@ -1073,7 +1066,7 @@ build_explain_next_steps() {
local last_status failed_csv
last_status="$(build_last_status)"
failed_csv="$(build_failed_sources_csv)"
failed_csv="$(build_failed_active_sources_csv)"
if [ "${last_status:-}" = "success" ]; then
echo "👉 clashctl health"
@@ -1085,10 +1078,6 @@ build_explain_next_steps() {
echo "👉 clashctl health"
fi
if [ "$(subscription_mode 2>/dev/null || true)" = "merge" ]; then
echo "👉 clashctl config select list"
fi
echo "👉 clashctl doctor"
}
@@ -1289,38 +1278,6 @@ subscription_format_by_name() {
"$(yq_bin)" eval ".sources.${name}.type // \"clash\"" "$file" 2>/dev/null | head -n 1
}
merge_subscription_sources() {
local base_file="$1"
local sub_file="$2"
local out_file="$3"
local tmp_file
[ -s "$base_file" ] || die "基础配置不存在:$base_file"
[ -s "$sub_file" ] || die "订阅配置不存在:$sub_file"
tmp_file="$(mktemp)"
cp -f "$base_file" "$tmp_file"
BASE_FILE="$base_file" \
SUB_FILE="$sub_file" \
"$(yq_bin)" eval -i '
.proxies = (load(strenv(SUB_FILE)).proxies // []) |
.["proxy-groups"] = (load(strenv(SUB_FILE)).["proxy-groups"] // (.["proxy-groups"] // [])) |
.["rule-providers"] = ((.["rule-providers"] // {}) * (load(strenv(SUB_FILE)).["rule-providers"] // {})) |
.rules = (load(strenv(SUB_FILE)).rules // (.rules // []))
' "$tmp_file" || {
rm -f "$tmp_file" 2>/dev/null || true
die "合并订阅配置失败"
}
[ -s "$tmp_file" ] || {
rm -f "$tmp_file" 2>/dev/null || true
die "合并订阅配置失败:输出为空"
}
mv -f "$tmp_file" "$out_file"
}
is_valid_port_number() {
local port="$1"
@@ -1817,8 +1774,7 @@ remove_subscription() {
active="$(active_subscription_name)"
NAME="$name" "$(yq_bin)" eval -i '
del(.sources[env(NAME)]) |
.selected = ((.selected // []) | map(select(. != env(NAME))))
del(.sources[env(NAME)])
' "$file"
if [ "$active" = "$name" ]; then
@@ -1861,7 +1817,6 @@ rename_subscription() {
OLD_NAME="$old_name" NEW_NAME="$new_name" "$(yq_bin)" eval -i '
.sources[env(NEW_NAME)] = .sources[env(OLD_NAME)] |
del(.sources[env(OLD_NAME)]) |
.selected = ((.selected // []) | map(if . == env(OLD_NAME) then env(NEW_NAME) else . end)) |
.active = (if .active == env(OLD_NAME) then env(NEW_NAME) else .active end)
' "$file"
@@ -2356,11 +2311,10 @@ list_subscriptions_verbose() {
}
subscription_list_overview_lines() {
local active recommended mode
local active recommended
active="$(active_subscription_name 2>/dev/null || true)"
recommended="$(recommended_subscription_name 2>/dev/null || true)"
mode="$(subscription_mode 2>/dev/null || true)"
if [ -n "${active:-}" ]; then
echo "📡 当前主订阅:$active"
@@ -2376,17 +2330,14 @@ subscription_list_overview_lines() {
echo "💡 推荐订阅:暂无"
fi
if [ -n "${mode:-}" ]; then
echo "🧩 当前模式:$mode"
fi
echo "🧩 编译模式:active-only"
}
subscription_list_recommendation_lines() {
local active recommended mode
local active recommended
active="$(active_subscription_name 2>/dev/null || true)"
recommended="$(recommended_subscription_name 2>/dev/null || true)"
mode="$(subscription_mode 2>/dev/null || true)"
if [ -n "${active:-}" ] && ! active_subscription_enabled 2>/dev/null; then
echo "👉 clashctl use"
@@ -2402,13 +2353,6 @@ subscription_list_recommendation_lines() {
return 0
fi
if [ "${mode:-}" = "merge" ]; then
echo "👉 clashctl config show"
echo "👉 clashctl health"
echo "👉 clashctl ls --verbose"
return 0
fi
echo "👉 clashctl use"
echo "👉 clashctl health"
echo "👉 clashctl ls --verbose"
@@ -2964,14 +2908,6 @@ profile_set_value() {
# ===== FINAL OVERRIDE: active-only runtime pipeline =====
subscription_mode() {
echo "single"
}
subscription_policy() {
echo "active-only"
}
resolve_build_sources() {
local active
@@ -3153,4 +3089,4 @@ generate_config() {
regenerate_config() {
generate_config
}
}