mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 12:13:04 +08:00
Merge pull request #167 from wnlen/codex/refactor-to-active-only-architecture-qpp4w8
Add local Dashboard deployment, controller secret & shell-proxy persistence; improve diagnostics
This commit is contained in:
+5
-1
@@ -17,6 +17,7 @@ detect_install_scope "${1:-auto}"
|
||||
|
||||
init_layout
|
||||
ensure_required_commands
|
||||
ensure_dashboard_deploy_prerequisites
|
||||
|
||||
ui_download "正在准备安装依赖"
|
||||
resolve_runtime_kernel
|
||||
@@ -30,6 +31,9 @@ mark_install_port_plan || true
|
||||
install_clashctl_entry
|
||||
install_shell_alias_entry
|
||||
install_runtime_entry
|
||||
install_local_dashboard_assets
|
||||
ensure_controller_secret >/dev/null
|
||||
set_shell_proxy_persist_enabled "false"
|
||||
|
||||
ensure_subscription_bootstrap_for_install "default"
|
||||
prompt_subscription_if_needed
|
||||
@@ -56,4 +60,4 @@ if [ -n "$(subscription_url 2>/dev/null || true)" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
print_install_summary
|
||||
print_install_summary
|
||||
|
||||
+48
-1
@@ -9,6 +9,37 @@ _clashctl_real() {
|
||||
command clashctl "$@"
|
||||
}
|
||||
|
||||
_clash_alias_project_dir() {
|
||||
local self_dir
|
||||
self_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
echo "$self_dir"
|
||||
}
|
||||
|
||||
_clash_alias_state_file() {
|
||||
echo "$(_clash_alias_project_dir)/runtime/shell-proxy.env"
|
||||
}
|
||||
|
||||
_clash_alias_set_persist_enabled() {
|
||||
local enabled="$1"
|
||||
local state_file
|
||||
state_file="$(_clash_alias_state_file)"
|
||||
|
||||
mkdir -p "$(dirname "$state_file")"
|
||||
cat > "$state_file" <<EOF
|
||||
SHELL_PROXY_PERSIST_ENABLED="${enabled}"
|
||||
SHELL_PROXY_PERSIST_TIME="$(date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || true)"
|
||||
EOF
|
||||
}
|
||||
|
||||
_clash_alias_persist_enabled() {
|
||||
local state_file enabled
|
||||
state_file="$(_clash_alias_state_file)"
|
||||
[ -f "$state_file" ] || return 1
|
||||
|
||||
enabled="$(sed -nE 's/^SHELL_PROXY_PERSIST_ENABLED=\"?([^\"\r\n]+)\"?$/\1/p' "$state_file" | head -n 1)"
|
||||
[ "${enabled:-false}" = "true" ]
|
||||
}
|
||||
|
||||
_clash_alias_print_sep() {
|
||||
echo
|
||||
}
|
||||
@@ -37,6 +68,7 @@ _clash_alias_prepare_on() {
|
||||
}
|
||||
|
||||
_clash_alias_after_on() {
|
||||
_clash_alias_set_persist_enabled "true"
|
||||
_clash_alias_proxy_on || return $?
|
||||
|
||||
_clash_alias_print_sep
|
||||
@@ -46,9 +78,11 @@ _clash_alias_after_on() {
|
||||
}
|
||||
|
||||
_clash_alias_after_off() {
|
||||
_clash_alias_set_persist_enabled "false"
|
||||
_clash_alias_print_sep
|
||||
echo "🔴 已关闭代理环境"
|
||||
echo "🧹 当前 Shell 代理变量已清理"
|
||||
echo "🧭 新终端默认代理:已关闭"
|
||||
echo "👉 下一步:clashctl status"
|
||||
}
|
||||
|
||||
@@ -62,7 +96,18 @@ _clash_alias_run_on() {
|
||||
|
||||
_clash_alias_run_off() {
|
||||
_clash_alias_proxy_off
|
||||
_clash_alias_set_persist_enabled "false"
|
||||
_clashctl_real off "$@" || return $?
|
||||
_clash_alias_after_off
|
||||
}
|
||||
|
||||
_clash_alias_auto_restore_proxy() {
|
||||
[ "${CLASH_FOR_LINUX_PROXY_AUTO_RESTORED:-0}" = "1" ] && return 0
|
||||
export CLASH_FOR_LINUX_PROXY_AUTO_RESTORED="1"
|
||||
|
||||
if _clash_alias_persist_enabled; then
|
||||
_clash_alias_proxy_on >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
clashctl() {
|
||||
@@ -195,4 +240,6 @@ clashmixin() {
|
||||
esac
|
||||
}
|
||||
|
||||
# shell 被 source 时不自动执行任何代理动作
|
||||
# shell 被 source 后做轻量恢复:
|
||||
# 若上次 clashon 持久化开启,则新终端自动恢复当前 shell 代理变量。
|
||||
_clash_alias_auto_restore_proxy
|
||||
|
||||
+243
-15
@@ -111,6 +111,23 @@ prepare() {
|
||||
detect_install_scope auto
|
||||
}
|
||||
|
||||
ensure_add_use_prerequisites() {
|
||||
if [ ! -x "$(yq_bin)" ]; then
|
||||
die_state "依赖未就绪:缺少 yq($(yq_bin))" \
|
||||
"请先执行 bash install.sh,或运行 clashctl doctor 查看缺失项"
|
||||
fi
|
||||
|
||||
if [ ! -d "$RUNTIME_DIR" ]; then
|
||||
die_state "运行环境未初始化:缺少 runtime 目录" \
|
||||
"请先执行 bash install.sh"
|
||||
fi
|
||||
|
||||
if [ ! -d "$CONFIG_DIR" ]; then
|
||||
die_state "运行环境未初始化:缺少 config 目录" \
|
||||
"请先执行 bash install.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_runtime_ports_ready() {
|
||||
local mixed_port controller_port dns_port
|
||||
local new_mixed_port="" new_controller_port="" new_dns_port=""
|
||||
@@ -190,10 +207,12 @@ ensure_on_path_ready() {
|
||||
}
|
||||
|
||||
print_on_feedback() {
|
||||
local mixed_port controller next_action
|
||||
local mixed_port controller controller_lan controller_public next_action
|
||||
|
||||
mixed_port="$(status_read_mixed_port 2>/dev/null || true)"
|
||||
controller="$(status_read_controller 2>/dev/null || true)"
|
||||
controller_lan="$(status_read_controller_lan 2>/dev/null || true)"
|
||||
controller_public="$(status_read_controller_public 2>/dev/null || true)"
|
||||
load_system_state
|
||||
next_action="$(system_state_default_action 2>/dev/null || echo 'clashctl status')"
|
||||
|
||||
@@ -209,6 +228,12 @@ print_on_feedback() {
|
||||
|
||||
if [ -n "${controller:-}" ] && [ "$controller" != "null" ]; then
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
[ -n "${controller_lan:-}" ] && echo "🏠 局域网:http://${controller_lan}/ui"
|
||||
if [ -n "${controller_public:-}" ]; then
|
||||
echo "🌍 公网:http://${controller_public}/ui"
|
||||
else
|
||||
echo "🌍 公网:需公网 IP / 端口映射后可访问"
|
||||
fi
|
||||
else
|
||||
echo "🖥️ 控制台:未知"
|
||||
fi
|
||||
@@ -243,11 +268,18 @@ cmd_off() {
|
||||
}
|
||||
|
||||
ui_internal_url() {
|
||||
local controller
|
||||
local controller host port
|
||||
controller="$(status_read_controller 2>/dev/null || true)"
|
||||
|
||||
[ -n "${controller:-}" ] && [ "$controller" != "null" ] || return 1
|
||||
echo "http://${controller}/ui"
|
||||
host="${controller%:*}"
|
||||
port="${controller##*:}"
|
||||
|
||||
if [ "${host:-}" = "0.0.0.0" ]; then
|
||||
host="127.0.0.1"
|
||||
fi
|
||||
|
||||
echo "http://${host}:${port}/ui"
|
||||
}
|
||||
|
||||
ui_public_url() {
|
||||
@@ -276,6 +308,7 @@ cmd_ui() {
|
||||
local controller_addr=""
|
||||
local internal_url="" lan_url="" public_url="" public_fixed_url=""
|
||||
local current_secret="" controller_port="" controller_status=""
|
||||
local dashboard_source="" dashboard_ready_text=""
|
||||
|
||||
prepare
|
||||
runtime_config_exists || die "🧩 运行时配置不存在,请先生成配置"
|
||||
@@ -289,6 +322,16 @@ cmd_ui() {
|
||||
public_fixed_url="${CLASH_PUBLIC_UI_URL:-http://board.zash.run.place}"
|
||||
current_secret="$(controller_secret 2>/dev/null || true)"
|
||||
controller_port="$(ui_controller_port 2>/dev/null || true)"
|
||||
dashboard_source="$(read_runtime_value "DASHBOARD_ASSET_SOURCE" 2>/dev/null || echo none)"
|
||||
case "${dashboard_source:-none}" in
|
||||
dir|zip|none) ;;
|
||||
*) dashboard_source="none" ;;
|
||||
esac
|
||||
if runtime_dashboard_ready; then
|
||||
dashboard_ready_text="有效"
|
||||
else
|
||||
dashboard_ready_text="无效"
|
||||
fi
|
||||
|
||||
if [ -z "${current_secret:-}" ] || [ "$current_secret" = "null" ]; then
|
||||
current_secret="未设置"
|
||||
@@ -313,6 +356,12 @@ cmd_ui() {
|
||||
"$current_secret" \
|
||||
"$controller_port"
|
||||
|
||||
ui_kv "🧩" "Dashboard 来源" "$dashboard_source"
|
||||
ui_kv "🧩" "Dashboard 部署" "$dashboard_ready_text"
|
||||
if [ "$dashboard_ready_text" != "有效" ]; then
|
||||
ui_warn "本地 Dashboard 部署无效,请先修复 assets 后重试 install/update"
|
||||
fi
|
||||
|
||||
case "$controller_status" in
|
||||
可访问)
|
||||
ui_next "浏览器打开上面的任一地址"
|
||||
@@ -359,13 +408,11 @@ status_build_active_sources() {
|
||||
|
||||
status_build_failed_active_sources() {
|
||||
local value
|
||||
|
||||
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
|
||||
}
|
||||
@@ -1670,9 +1717,9 @@ status_port_adjustment_brief() {
|
||||
}
|
||||
|
||||
print_status_summary_compact() {
|
||||
local profile mixed_port controller
|
||||
local running_text user_connectivity user_risk current_proxy_brief next_action
|
||||
local current_active
|
||||
local profile mixed_port controller controller_lan controller_public
|
||||
local running_text user_connectivity user_risk current_proxy_brief next_action shell_persist_text
|
||||
local current_active dashboard_text dashboard_source_text dashboard_policy_text secret_text
|
||||
local tun_text
|
||||
|
||||
profile="$(show_active_profile 2>/dev/null || true)"
|
||||
@@ -1680,6 +1727,8 @@ print_status_summary_compact() {
|
||||
|
||||
mixed_port="$(status_read_mixed_port 2>/dev/null || true)"
|
||||
controller="$(status_read_controller 2>/dev/null || true)"
|
||||
controller_lan="$(status_read_controller_lan 2>/dev/null || true)"
|
||||
controller_public="$(status_read_controller_public 2>/dev/null || true)"
|
||||
current_active="$(active_subscription_name 2>/dev/null || true)"
|
||||
tun_text="$(status_tun_effective_text)"
|
||||
|
||||
@@ -1693,6 +1742,31 @@ print_status_summary_compact() {
|
||||
user_risk="$(status_user_risk_text)"
|
||||
current_proxy_brief="$(status_current_proxy_brief)"
|
||||
next_action="$(system_state_default_action 2>/dev/null || echo 'clashctl status')"
|
||||
if shell_proxy_persist_enabled 2>/dev/null; then
|
||||
shell_persist_text="开启"
|
||||
else
|
||||
shell_persist_text="关闭"
|
||||
fi
|
||||
if [ -f "$(runtime_dashboard_dir)/index.html" ]; then
|
||||
dashboard_text="已部署"
|
||||
else
|
||||
dashboard_text="未部署"
|
||||
fi
|
||||
dashboard_source_text="$(read_runtime_value "DASHBOARD_ASSET_SOURCE" 2>/dev/null || echo none)"
|
||||
case "${dashboard_source_text:-none}" in
|
||||
dir|zip|none) ;;
|
||||
*) dashboard_source_text="none" ;;
|
||||
esac
|
||||
if [ "$dashboard_source_text" = "none" ]; then
|
||||
dashboard_policy_text="默认策略:Dashboard 资产无效将阻断 install/update"
|
||||
else
|
||||
dashboard_policy_text="默认策略:Dashboard 资产需保持可部署"
|
||||
fi
|
||||
if [ -n "$(read_env_value "CLASH_CONTROLLER_SECRET" 2>/dev/null || true)" ]; then
|
||||
secret_text="已设置"
|
||||
else
|
||||
secret_text="未设置"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "😼 Clash 状态总览"
|
||||
@@ -1712,6 +1786,10 @@ print_status_summary_compact() {
|
||||
echo "⚙️ 运行后端:$(status_runtime_backend_text)"
|
||||
echo "🧪 环境模式:$(status_container_mode_text)"
|
||||
echo "🧪 Tun 状态:${tun_text:-未知}"
|
||||
echo "🧭 新终端代理继承:${shell_persist_text}"
|
||||
echo "🧩 Dashboard:${dashboard_text}(来源:${dashboard_source_text})"
|
||||
echo "🧩 Dashboard 策略:${dashboard_policy_text}"
|
||||
echo "🔐 控制器密钥:${secret_text}"
|
||||
|
||||
if [ -n "${mixed_port:-}" ] && [ "$mixed_port" != "null" ]; then
|
||||
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
|
||||
@@ -1721,6 +1799,12 @@ print_status_summary_compact() {
|
||||
|
||||
if [ -n "${controller:-}" ] && [ "$controller" != "null" ]; then
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
[ -n "${controller_lan:-}" ] && echo "🏠 局域网:http://${controller_lan}/ui"
|
||||
if [ -n "${controller_public:-}" ]; then
|
||||
echo "🌍 公网:http://${controller_public}/ui"
|
||||
else
|
||||
echo "🌍 公网:需公网 IP / 端口映射后可访问"
|
||||
fi
|
||||
else
|
||||
echo "🖥️ 控制台:未知"
|
||||
fi
|
||||
@@ -1732,7 +1816,7 @@ print_status_summary_compact() {
|
||||
}
|
||||
|
||||
print_status_summary_verbose() {
|
||||
local running_text profile mixed_port controller
|
||||
local running_text profile mixed_port controller controller_lan controller_public
|
||||
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
|
||||
@@ -1742,12 +1826,15 @@ print_status_summary_verbose() {
|
||||
local config_source config_source_time build_applied build_applied_time build_applied_reason
|
||||
local install_backend_text install_container_text install_verify_text port_adjustment_text
|
||||
local tun_enabled tun_effective tun_stack tun_container_text tun_kernel_text tun_verify_result tun_verify_reason tun_verify_time
|
||||
local shell_persist_text dashboard_text dashboard_source_text dashboard_policy_text secret_text
|
||||
|
||||
profile="$(show_active_profile 2>/dev/null || true)"
|
||||
[ -n "${profile:-}" ] || profile="default"
|
||||
|
||||
mixed_port="$(status_read_mixed_port 2>/dev/null || true)"
|
||||
controller="$(status_read_controller 2>/dev/null || true)"
|
||||
controller_lan="$(status_read_controller_lan 2>/dev/null || true)"
|
||||
controller_public="$(status_read_controller_public 2>/dev/null || true)"
|
||||
|
||||
|
||||
current_active="$(active_subscription_name 2>/dev/null || true)"
|
||||
@@ -1801,6 +1888,31 @@ print_status_summary_verbose() {
|
||||
tun_verify_result="$(status_tun_last_verify_result 2>/dev/null || true)"
|
||||
tun_verify_reason="$(status_tun_last_verify_reason 2>/dev/null || true)"
|
||||
tun_verify_time="$(status_tun_last_verify_time 2>/dev/null || true)"
|
||||
if shell_proxy_persist_enabled 2>/dev/null; then
|
||||
shell_persist_text="开启"
|
||||
else
|
||||
shell_persist_text="关闭"
|
||||
fi
|
||||
if [ -f "$(runtime_dashboard_dir)/index.html" ]; then
|
||||
dashboard_text="已部署"
|
||||
else
|
||||
dashboard_text="未部署"
|
||||
fi
|
||||
dashboard_source_text="$(read_runtime_value "DASHBOARD_ASSET_SOURCE" 2>/dev/null || echo none)"
|
||||
case "${dashboard_source_text:-none}" in
|
||||
dir|zip|none) ;;
|
||||
*) dashboard_source_text="none" ;;
|
||||
esac
|
||||
if [ "$dashboard_source_text" = "none" ]; then
|
||||
dashboard_policy_text="默认策略:Dashboard 资产无效将阻断 install/update"
|
||||
else
|
||||
dashboard_policy_text="默认策略:Dashboard 资产需保持可部署"
|
||||
fi
|
||||
if [ -n "$(read_env_value "CLASH_CONTROLLER_SECRET" 2>/dev/null || true)" ]; then
|
||||
secret_text="已设置"
|
||||
else
|
||||
secret_text="未设置"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "😼 Clash 状态总览"
|
||||
@@ -1825,6 +1937,12 @@ print_status_summary_verbose() {
|
||||
|
||||
if [ -n "${controller:-}" ] && [ "$controller" != "null" ]; then
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
[ -n "${controller_lan:-}" ] && echo "🏠 局域网:http://${controller_lan}/ui"
|
||||
if [ -n "${controller_public:-}" ]; then
|
||||
echo "🌍 公网:http://${controller_public}/ui"
|
||||
else
|
||||
echo "🌍 公网:需公网 IP / 端口映射后可访问"
|
||||
fi
|
||||
else
|
||||
echo "🖥️ 控制台:未知"
|
||||
fi
|
||||
@@ -1836,10 +1954,14 @@ print_status_summary_verbose() {
|
||||
echo "🧪 环境模式:${install_container_text:-unknown}"
|
||||
echo "🧩 安装验证:${install_verify_text:-unknown}"
|
||||
echo "🧭 端口裁决:${port_adjustment_text:-unknown}"
|
||||
echo "🧭 新终端代理继承:${shell_persist_text}"
|
||||
echo "🧩 Dashboard:${dashboard_text}(来源:${dashboard_source_text})"
|
||||
echo "🧩 Dashboard 策略:${dashboard_policy_text}"
|
||||
echo "🔐 控制器密钥:${secret_text}"
|
||||
echo
|
||||
|
||||
if [ -n "$(install_plan_controller 2>/dev/null || true)" ]; then
|
||||
echo "🖥️ 安装期控制器:$(install_plan_controller 2>/dev/null || true)"
|
||||
echo "🖥️ 安装期控制器:$(display_controller_local_addr "$(install_plan_controller 2>/dev/null || true)" 2>/dev/null || install_plan_controller 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -n "$(install_plan_mixed_port 2>/dev/null || true)" ]; then
|
||||
@@ -2244,15 +2366,46 @@ doctor_container_tun() {
|
||||
}
|
||||
|
||||
doctor_dependencies() {
|
||||
local dashboard_source
|
||||
|
||||
doctor_print_title "依赖检查"
|
||||
|
||||
[ -x "$(mihomo_bin)" ] && doctor_ok "Mihomo 已安装:$(mihomo_bin)" || doctor_fail "Mihomo 缺失:$(mihomo_bin)"
|
||||
[ -x "$(subconverter_bin)" ] && doctor_ok "subconverter 已安装:$(subconverter_bin)" || doctor_fail "subconverter 缺失:$(subconverter_bin)"
|
||||
[ -x "$(yq_bin)" ] && doctor_ok "yq 已安装:$(yq_bin)" || doctor_fail "yq 缺失:$(yq_bin)"
|
||||
|
||||
dashboard_source="$(dashboard_asset_source)"
|
||||
case "$dashboard_source" in
|
||||
dir)
|
||||
doctor_ok "Dashboard 来源:dir(当前部署不依赖 unzip)"
|
||||
;;
|
||||
zip)
|
||||
if command -v unzip >/dev/null 2>&1; then
|
||||
if dashboard_archive_valid; then
|
||||
doctor_ok "Dashboard 来源:zip(unzip 可用,压缩包可解压)"
|
||||
else
|
||||
doctor_fail "Dashboard 来源:zip(压缩包损坏或不可解压,将阻断 install/update)"
|
||||
fi
|
||||
else
|
||||
doctor_fail "Dashboard 来源:zip(缺少 unzip,无法部署,将阻断 install/update)"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
doctor_warn "Dashboard 来源:none(dist/ 与 dist.zip 均不可用,将阻断 install/update)"
|
||||
;;
|
||||
esac
|
||||
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
doctor_ok "Secret 生成:openssl 可用"
|
||||
elif [ -r /dev/urandom ] && command -v od >/dev/null 2>&1 && command -v tr >/dev/null 2>&1 && command -v head >/dev/null 2>&1; then
|
||||
doctor_ok "Secret 生成:fallback 可用(/dev/urandom + od/tr/head)"
|
||||
else
|
||||
doctor_fail "Secret 生成:缺少 openssl 且 fallback 不可用"
|
||||
fi
|
||||
}
|
||||
|
||||
doctor_config() {
|
||||
local config_file active_profile mixed_port controller
|
||||
local config_file active_profile mixed_port controller controller_secret_value external_ui_path dashboard_source
|
||||
|
||||
doctor_print_title "配置检查"
|
||||
|
||||
@@ -2289,11 +2442,30 @@ doctor_config() {
|
||||
fi
|
||||
|
||||
if [ -n "${controller:-}" ] && [ "$controller" != "null" ]; then
|
||||
doctor_ok "控制器地址:$controller"
|
||||
doctor_ok "控制器地址:$(display_controller_local_addr "$controller" 2>/dev/null || echo "$controller")"
|
||||
else
|
||||
doctor_warn "未解析到控制器地址"
|
||||
fi
|
||||
|
||||
controller_secret_value="$("$(yq_bin)" eval '.secret // ""' "$config_file" 2>/dev/null | head -n 1)"
|
||||
if [ -n "${controller_secret_value:-}" ] && [ "$controller_secret_value" != "null" ]; then
|
||||
doctor_ok "控制器密钥:已设置"
|
||||
else
|
||||
doctor_fail "控制器密钥:未设置"
|
||||
fi
|
||||
|
||||
external_ui_path="$("$(yq_bin)" eval '.["external-ui"] // ""' "$config_file" 2>/dev/null | head -n 1)"
|
||||
dashboard_source="$(read_runtime_value "DASHBOARD_ASSET_SOURCE" 2>/dev/null || echo none)"
|
||||
case "${dashboard_source:-none}" in
|
||||
dir|zip|none) ;;
|
||||
*) dashboard_source="none" ;;
|
||||
esac
|
||||
if [ -n "${external_ui_path:-}" ] && [ "$external_ui_path" != "null" ] && [ -f "${external_ui_path%/}/index.html" ]; then
|
||||
doctor_ok "Dashboard 已接入:${external_ui_path}(来源:${dashboard_source})"
|
||||
else
|
||||
doctor_warn "Dashboard 未接入或目录无效(来源:${dashboard_source})"
|
||||
fi
|
||||
|
||||
if test_runtime_config "$config_file" >/dev/null 2>&1; then
|
||||
doctor_ok "配置校验通过"
|
||||
else
|
||||
@@ -2572,6 +2744,7 @@ doctor_install_ports() {
|
||||
doctor_runtime_events() {
|
||||
local fallback_used fallback_time fallback_reason risk_level
|
||||
local config_source build_applied build_applied_time build_applied_reason
|
||||
local shell_persist_text dashboard_status dashboard_source secret_status
|
||||
|
||||
doctor_print_title "运行事件检查"
|
||||
|
||||
@@ -2583,9 +2756,32 @@ doctor_runtime_events() {
|
||||
build_applied="$(status_runtime_build_applied 2>/dev/null || true)"
|
||||
build_applied_time="$(status_runtime_build_applied_time 2>/dev/null || true)"
|
||||
build_applied_reason="$(status_runtime_build_applied_reason 2>/dev/null || true)"
|
||||
if shell_proxy_persist_enabled 2>/dev/null; then
|
||||
shell_persist_text="开启"
|
||||
else
|
||||
shell_persist_text="关闭"
|
||||
fi
|
||||
if [ -f "$(runtime_dashboard_dir)/index.html" ]; then
|
||||
dashboard_status="已部署"
|
||||
else
|
||||
dashboard_status="未部署"
|
||||
fi
|
||||
dashboard_source="$(read_runtime_value "DASHBOARD_ASSET_SOURCE" 2>/dev/null || echo none)"
|
||||
case "${dashboard_source:-none}" in
|
||||
dir|zip|none) ;;
|
||||
*) dashboard_source="none" ;;
|
||||
esac
|
||||
if [ -n "$(read_env_value "CLASH_CONTROLLER_SECRET" 2>/dev/null || true)" ]; then
|
||||
secret_status="已设置"
|
||||
else
|
||||
secret_status="未设置"
|
||||
fi
|
||||
|
||||
doctor_ok "当前风险等级:${risk_level:-unknown}"
|
||||
doctor_ok "当前配置来源:${config_source:-unknown}"
|
||||
doctor_ok "新终端代理继承:${shell_persist_text}"
|
||||
doctor_ok "Dashboard 运行目录:${dashboard_status}(来源:${dashboard_source})"
|
||||
doctor_ok ".env 控制器密钥:${secret_status}"
|
||||
|
||||
case "${build_applied:-}" in
|
||||
true)
|
||||
@@ -2691,7 +2887,7 @@ doctor_controller() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
doctor_ok "控制器地址:$controller"
|
||||
doctor_ok "控制器地址:$(display_controller_local_addr "$controller" 2>/dev/null || echo "$controller")"
|
||||
|
||||
if ! status_is_running; then
|
||||
doctor_warn "内核未运行,无法检查控制器 API"
|
||||
@@ -3269,7 +3465,7 @@ doctor_evidence_lines() {
|
||||
fi
|
||||
|
||||
if [ -n "${controller:-}" ] && [ "$controller" != "null" ]; then
|
||||
echo "🔍 控制器地址:${controller}"
|
||||
echo "🔍 控制器地址:$(display_controller_local_addr "$controller" 2>/dev/null || echo "$controller")"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -3283,6 +3479,8 @@ set_controller_secret() {
|
||||
SECRET_VALUE="$secret" "$(yq_bin)" eval -i '
|
||||
.secret = strenv(SECRET_VALUE)
|
||||
' "$file"
|
||||
|
||||
write_env_value "CLASH_CONTROLLER_SECRET" "$secret"
|
||||
}
|
||||
|
||||
cmd_secret() {
|
||||
@@ -3884,6 +4082,7 @@ cmd_add() {
|
||||
local sub_name
|
||||
|
||||
prepare
|
||||
ensure_add_use_prerequisites
|
||||
|
||||
[ -n "${1:-}" ] || die "用法:clashctl add <url> [clash|convert] [name]"
|
||||
|
||||
@@ -3898,6 +4097,7 @@ cmd_use() {
|
||||
local recommended active
|
||||
|
||||
prepare
|
||||
ensure_add_use_prerequisites
|
||||
|
||||
case "${1:-}" in
|
||||
--recommend|-r)
|
||||
@@ -4684,10 +4884,38 @@ status_read_mixed_port() {
|
||||
runtime_config_mixed_port 2>/dev/null || true
|
||||
}
|
||||
|
||||
status_read_controller() {
|
||||
status_read_controller_raw() {
|
||||
runtime_config_controller_addr 2>/dev/null || true
|
||||
}
|
||||
|
||||
status_read_controller() {
|
||||
local controller
|
||||
controller="$(status_read_controller_raw 2>/dev/null || true)"
|
||||
display_controller_local_addr "$controller" 2>/dev/null || echo "$controller"
|
||||
}
|
||||
|
||||
status_read_controller_lan() {
|
||||
local controller lan_ip port
|
||||
controller="$(status_read_controller_raw 2>/dev/null || true)"
|
||||
[ -n "${controller:-}" ] && [ "$controller" != "null" ] || return 1
|
||||
|
||||
port="${controller##*:}"
|
||||
lan_ip="$(ui_lan_ip 2>/dev/null || true)"
|
||||
[ -n "${lan_ip:-}" ] || return 1
|
||||
echo "${lan_ip}:${port}"
|
||||
}
|
||||
|
||||
status_read_controller_public() {
|
||||
local controller public_ip port
|
||||
controller="$(status_read_controller_raw 2>/dev/null || true)"
|
||||
[ -n "${controller:-}" ] && [ "$controller" != "null" ] || return 1
|
||||
|
||||
port="${controller##*:}"
|
||||
public_ip="$(ui_public_ip 2>/dev/null || true)"
|
||||
[ -n "${public_ip:-}" ] || return 1
|
||||
echo "${public_ip}:${port}"
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
shift || true
|
||||
|
||||
|
||||
+195
-8
@@ -281,6 +281,11 @@ normalize_env_compat() {
|
||||
CLASH_SUBSCRIPTION_UA="$CLASH_SUB_UA"
|
||||
fi
|
||||
|
||||
|
||||
# 旧默认值迁移:公网默认监听
|
||||
if [ "${EXTERNAL_CONTROLLER:-}" = "127.0.0.1:9090" ]; then
|
||||
EXTERNAL_CONTROLLER="0.0.0.0:9090"
|
||||
fi
|
||||
# 已废弃:active-only 主链不再消费该字段
|
||||
unset BUILD_MIN_SUCCESS_SOURCES 2>/dev/null || true
|
||||
|
||||
@@ -295,6 +300,10 @@ cleanup_env_legacy_compat_fields() {
|
||||
|
||||
awk '
|
||||
$0 ~ /^[[:space:]]*(export[[:space:]]+)?BUILD_MIN_SUCCESS_SOURCES=/ { next }
|
||||
$0 ~ /^[[:space:]]*(export[[:space:]]+)?EXTERNAL_CONTROLLER="?127\.0\.0\.1:9090"?$/ {
|
||||
print "export EXTERNAL_CONTROLLER=\"0.0.0.0:9090\""
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||
}
|
||||
@@ -755,7 +764,8 @@ download_file() {
|
||||
fi
|
||||
|
||||
rm -f "$fetch_tmp" 2>/dev/null || true
|
||||
die "下载失败:${asset_name}"
|
||||
die_state "下载失败:${asset_name}" \
|
||||
"请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clashctl doctor"
|
||||
fi
|
||||
|
||||
ordered_entries="$(
|
||||
@@ -815,7 +825,8 @@ EOF
|
||||
done
|
||||
|
||||
rm -f "$fetch_tmp" 2>/dev/null || true
|
||||
die "下载失败:${asset_name}"
|
||||
die_state "下载失败:${asset_name}" \
|
||||
"请检查网络连通性,或在 .env 中配置下载源后重试;也可先执行 clashctl doctor"
|
||||
}
|
||||
|
||||
download_text_tmp_file() {
|
||||
@@ -929,6 +940,7 @@ init_layout() {
|
||||
"$PROJECT_DIR/scripts/core" \
|
||||
"$PROJECT_DIR/scripts/init" \
|
||||
"$RUNTIME_DIR" \
|
||||
"$RUNTIME_DIR/dashboard" \
|
||||
"$BIN_DIR" \
|
||||
"$LOG_DIR"
|
||||
|
||||
@@ -942,6 +954,150 @@ ensure_required_commands() {
|
||||
command -v readlink >/dev/null 2>&1 || die "当前系统缺少 readlink"
|
||||
}
|
||||
|
||||
dashboard_archive_file() {
|
||||
echo "$RESOURCE_DIR/dashboard/dist.zip"
|
||||
}
|
||||
|
||||
dashboard_dir_file() {
|
||||
echo "$RESOURCE_DIR/dashboard/dist"
|
||||
}
|
||||
|
||||
runtime_dashboard_dir() {
|
||||
echo "$RUNTIME_DIR/dashboard"
|
||||
}
|
||||
|
||||
runtime_dashboard_ready() {
|
||||
[ -f "$(runtime_dashboard_dir)/index.html" ]
|
||||
}
|
||||
|
||||
dashboard_archive_valid() {
|
||||
local archive
|
||||
archive="$(dashboard_archive_file)"
|
||||
[ -f "$archive" ] || return 1
|
||||
unzip -tq "$archive" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
dashboard_dir_valid() {
|
||||
local dir
|
||||
dir="$(dashboard_dir_file)"
|
||||
[ -d "$dir" ] || return 1
|
||||
[ -f "$dir/index.html" ]
|
||||
}
|
||||
|
||||
dashboard_asset_source() {
|
||||
local archive
|
||||
archive="$(dashboard_archive_file)"
|
||||
|
||||
if dashboard_dir_valid; then
|
||||
echo "dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$archive" ]; then
|
||||
echo "zip"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "none"
|
||||
}
|
||||
|
||||
ensure_dashboard_deploy_prerequisites() {
|
||||
local archive source_type
|
||||
archive="$(dashboard_archive_file)"
|
||||
source_type="$(dashboard_asset_source)"
|
||||
|
||||
case "$source_type" in
|
||||
dir)
|
||||
return 0
|
||||
;;
|
||||
zip)
|
||||
command -v unzip >/dev/null 2>&1 || die_state \
|
||||
"检测到 Dashboard 仅可从 dist.zip 部署,但系统缺少 unzip" \
|
||||
"请安装 unzip,或提供 resources/dashboard/dist/index.html(默认策略:本地 Dashboard 资产无效将阻断 install/update)"
|
||||
dashboard_archive_valid || die_state \
|
||||
"Dashboard 压缩包不可用:$archive" \
|
||||
"请修复 dist.zip,或提供 resources/dashboard/dist/index.html(默认策略:本地 Dashboard 资产无效将阻断 install/update)"
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
die_state "本地 Dashboard 资产不可用(dist/ 与 dist.zip 均无效)" \
|
||||
"请提供 resources/dashboard/dist/index.html 或可解压的 resources/dashboard/dist.zip(默认策略:本地 Dashboard 资产无效将阻断 install/update)"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
shell_proxy_persist_state_file() {
|
||||
echo "$RUNTIME_DIR/shell-proxy.env"
|
||||
}
|
||||
|
||||
shell_proxy_persist_enabled() {
|
||||
local file enabled
|
||||
file="$(shell_proxy_persist_state_file)"
|
||||
[ -f "$file" ] || return 1
|
||||
|
||||
enabled="$(sed -nE 's/^SHELL_PROXY_PERSIST_ENABLED=\"?([^\"\r\n]+)\"?$/\1/p' "$file" | head -n 1)"
|
||||
[ "${enabled:-false}" = "true" ]
|
||||
}
|
||||
|
||||
set_shell_proxy_persist_enabled() {
|
||||
local enabled="${1:-false}"
|
||||
local file
|
||||
file="$(shell_proxy_persist_state_file)"
|
||||
|
||||
mkdir -p "$(dirname "$file")"
|
||||
cat > "$file" <<EOF
|
||||
SHELL_PROXY_PERSIST_ENABLED="${enabled}"
|
||||
SHELL_PROXY_PERSIST_TIME="$(now_datetime)"
|
||||
EOF
|
||||
}
|
||||
|
||||
clear_shell_proxy_persist_state() {
|
||||
rm -f "$(shell_proxy_persist_state_file)" 2>/dev/null || true
|
||||
}
|
||||
|
||||
install_local_dashboard_assets() {
|
||||
local archive source_dir target source_type
|
||||
archive="$(dashboard_archive_file)"
|
||||
source_dir="$(dashboard_dir_file)"
|
||||
target="$(runtime_dashboard_dir)"
|
||||
source_type="$(dashboard_asset_source)"
|
||||
|
||||
rm -rf "$target" 2>/dev/null || true
|
||||
mkdir -p "$target"
|
||||
|
||||
case "$source_type" in
|
||||
dir)
|
||||
cp -a "$source_dir"/. "$target"/ 2>/dev/null || {
|
||||
write_runtime_value "DASHBOARD_ASSET_SOURCE" "dir"
|
||||
write_runtime_value "DASHBOARD_DEPLOY_READY" "false"
|
||||
die "复制 Dashboard 目录失败:$source_dir"
|
||||
}
|
||||
;;
|
||||
zip)
|
||||
unzip -oq "$archive" -d "$target" || {
|
||||
write_runtime_value "DASHBOARD_ASSET_SOURCE" "zip"
|
||||
write_runtime_value "DASHBOARD_DEPLOY_READY" "false"
|
||||
die "解压 Dashboard 失败:$archive"
|
||||
}
|
||||
;;
|
||||
*)
|
||||
write_runtime_value "DASHBOARD_ASSET_SOURCE" "none"
|
||||
write_runtime_value "DASHBOARD_DEPLOY_READY" "false"
|
||||
die_state "本地 Dashboard 资产不可用(dist/ 与 dist.zip 均无效)" \
|
||||
"请提供 resources/dashboard/dist/index.html 或可解压的 resources/dashboard/dist.zip"
|
||||
;;
|
||||
esac
|
||||
|
||||
if ! runtime_dashboard_ready; then
|
||||
write_runtime_value "DASHBOARD_ASSET_SOURCE" "$source_type"
|
||||
write_runtime_value "DASHBOARD_DEPLOY_READY" "false"
|
||||
die "Dashboard 部署不完整:缺少 $target/index.html"
|
||||
fi
|
||||
|
||||
write_runtime_value "DASHBOARD_ASSET_SOURCE" "$source_type"
|
||||
write_runtime_value "DASHBOARD_DEPLOY_READY" "true"
|
||||
}
|
||||
|
||||
get_os() {
|
||||
local os
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
@@ -996,6 +1152,17 @@ read_env_value() {
|
||||
sed -nE "s/^[[:space:]]*(export[[:space:]]+)?${key}=['\"]?([^'\"]*)['\"]?$/\2/p" "$file" | head -n 1
|
||||
}
|
||||
|
||||
unset_env_value() {
|
||||
local key="$1"
|
||||
local file="$PROJECT_DIR/.env"
|
||||
[ -f "$file" ] || return 0
|
||||
|
||||
awk -v k="$key" '
|
||||
$0 ~ "^[[:space:]]*(export[[:space:]]+)?" k "=" { next }
|
||||
{ print }
|
||||
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||
}
|
||||
|
||||
subscription_auto_update_enabled() {
|
||||
case "${CLASH_AUTO_UPDATE_SUBSCRIPTIONS:-true}" in
|
||||
true|1|yes|on)
|
||||
@@ -1321,6 +1488,24 @@ runtime_config_controller_port() {
|
||||
echo "${addr##*:}"
|
||||
}
|
||||
|
||||
display_controller_local_addr() {
|
||||
local controller="$1"
|
||||
local host port
|
||||
|
||||
[ -n "${controller:-}" ] || return 1
|
||||
[ "$controller" != "null" ] || return 1
|
||||
printf '%s' "$controller" | grep -q ':' || return 1
|
||||
|
||||
host="${controller%:*}"
|
||||
port="${controller##*:}"
|
||||
|
||||
if [ "$host" = "0.0.0.0" ]; then
|
||||
host="127.0.0.1"
|
||||
fi
|
||||
|
||||
echo "${host}:${port}"
|
||||
}
|
||||
|
||||
runtime_config_dns_listen() {
|
||||
local file
|
||||
file="$(runtime_config_file)"
|
||||
@@ -2306,7 +2491,7 @@ install_default_next_action() {
|
||||
}
|
||||
|
||||
install_runtime_brief_line() {
|
||||
local status_text mixed_port controller
|
||||
local status_text mixed_port controller controller_display
|
||||
|
||||
status_text="$(install_status_text)"
|
||||
mixed_port="$(install_plan_mixed_port 2>/dev/null || true)"
|
||||
@@ -2314,12 +2499,13 @@ install_runtime_brief_line() {
|
||||
|
||||
controller="$(install_plan_controller 2>/dev/null || true)"
|
||||
[ -n "${controller:-}" ] || controller="$(read_env_value "EXTERNAL_CONTROLLER" 2>/dev/null || echo "127.0.0.1:9090")"
|
||||
controller_display="$(display_controller_local_addr "$controller" 2>/dev/null || true)"
|
||||
|
||||
case "$status_text" in
|
||||
ready)
|
||||
echo "🟢 当前状态:ready"
|
||||
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
echo "🖥️ 控制台:http://${controller_display:-$controller}/ui"
|
||||
;;
|
||||
stopped)
|
||||
echo "🔴 当前状态:stopped"
|
||||
@@ -2331,7 +2517,7 @@ install_runtime_brief_line() {
|
||||
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
|
||||
fi
|
||||
if [ -n "${controller:-}" ]; then
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
echo "🖥️ 控制台:http://${controller_display:-$controller}/ui"
|
||||
fi
|
||||
;;
|
||||
broken)
|
||||
@@ -2344,7 +2530,7 @@ install_runtime_brief_line() {
|
||||
}
|
||||
|
||||
print_install_summary() {
|
||||
local backend mixed_port controller
|
||||
local backend mixed_port controller controller_display
|
||||
local has_subscription final_state next_action
|
||||
|
||||
backend="$(install_plan_backend 2>/dev/null || true)"
|
||||
@@ -2355,6 +2541,7 @@ print_install_summary() {
|
||||
|
||||
controller="$(install_plan_controller 2>/dev/null || true)"
|
||||
[ -n "${controller:-}" ] || controller="$(read_env_value "EXTERNAL_CONTROLLER" 2>/dev/null || echo "127.0.0.1:9090")"
|
||||
controller_display="$(display_controller_local_addr "$controller" 2>/dev/null || true)"
|
||||
|
||||
if install_has_subscription; then
|
||||
has_subscription="true"
|
||||
@@ -2375,7 +2562,7 @@ print_install_summary() {
|
||||
ready)
|
||||
echo "🚦 当前状态:🟢 ready"
|
||||
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
echo "🖥️ 控制台:http://${controller_display:-$controller}/ui"
|
||||
;;
|
||||
stopped)
|
||||
echo "🚦 当前状态:⚪ stopped"
|
||||
@@ -2387,7 +2574,7 @@ print_install_summary() {
|
||||
echo "🌐 本地代理:http://127.0.0.1:${mixed_port}"
|
||||
fi
|
||||
if [ -n "${controller:-}" ]; then
|
||||
echo "🖥️ 控制台:http://${controller}/ui"
|
||||
echo "🖥️ 控制台:http://${controller_display:-$controller}/ui"
|
||||
fi
|
||||
;;
|
||||
broken)
|
||||
|
||||
+55
-4
@@ -182,7 +182,7 @@ mixed-port: 7890
|
||||
allow-lan: true
|
||||
mode: rule
|
||||
log-level: info
|
||||
external-controller: 127.0.0.1:9090
|
||||
external-controller: 0.0.0.0:9090
|
||||
secret: ""
|
||||
|
||||
tun:
|
||||
@@ -431,7 +431,8 @@ clear_subscription_cache() {
|
||||
|
||||
normalize_runtime_config() {
|
||||
local file="$1"
|
||||
local mixed_port controller tun_enable_value tun_stack_value dns_port_value
|
||||
local mixed_port controller tun_enable_value tun_stack_value dns_port_value controller_secret_value
|
||||
local dashboard_dir_value
|
||||
local resolved_ports
|
||||
|
||||
[ -s "$file" ] || die "待规范化的配置文件不存在:$file"
|
||||
@@ -444,15 +445,22 @@ normalize_runtime_config() {
|
||||
tun_enable_value="$(tun_enabled)"
|
||||
tun_stack_value="$(tun_stack)"
|
||||
dns_port_value="$CLASH_DNS_PORT_RESOLVED"
|
||||
controller_secret_value="$(ensure_controller_secret)"
|
||||
dashboard_dir_value="$(runtime_dashboard_dir)"
|
||||
|
||||
mixed_port="$mixed_port" \
|
||||
controller="$controller" \
|
||||
tun_enable_value="$tun_enable_value" \
|
||||
tun_stack_value="$tun_stack_value" \
|
||||
controller_secret_value="$controller_secret_value" \
|
||||
dashboard_dir_value="$dashboard_dir_value" \
|
||||
dns_listen_value="0.0.0.0:${dns_port_value}" \
|
||||
"$(yq_bin)" eval -i '
|
||||
.["mixed-port"] = (env(mixed_port) | tonumber) |
|
||||
.["external-controller"] = env(controller) |
|
||||
.secret = env(controller_secret_value) |
|
||||
.["external-ui"] = env(dashboard_dir_value) |
|
||||
.["external-ui-url"] = "/ui" |
|
||||
.["allow-lan"] = (.["allow-lan"] // true) |
|
||||
.mode = (.mode // "rule") |
|
||||
.["log-level"] = (.["log-level"] // "info") |
|
||||
@@ -472,6 +480,49 @@ normalize_runtime_config() {
|
||||
' "$file"
|
||||
}
|
||||
|
||||
generate_secure_secret() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -hex 24 2>/dev/null | head -n 1
|
||||
return 0
|
||||
fi
|
||||
|
||||
head -c 32 /dev/urandom 2>/dev/null | od -An -tx1 | tr -d ' \n'
|
||||
}
|
||||
|
||||
is_valid_controller_secret() {
|
||||
local value="${1:-}"
|
||||
case "${value}" in
|
||||
""|null|NULL|undefined|UNDEFINED|\"\"|\'\')
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
ensure_controller_secret() {
|
||||
local value
|
||||
|
||||
value="${CLASH_CONTROLLER_SECRET:-}"
|
||||
if ! is_valid_controller_secret "$value"; then
|
||||
value="$(read_env_value "CLASH_CONTROLLER_SECRET" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if ! is_valid_controller_secret "$value"; then
|
||||
value="$(generate_secure_secret)"
|
||||
[ -n "${value:-}" ] || die "无法生成控制器密钥"
|
||||
write_env_value "CLASH_CONTROLLER_SECRET" "$value"
|
||||
fi
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
clear_controller_secret() {
|
||||
unset_env_value "CLASH_CONTROLLER_SECRET" || true
|
||||
unset CLASH_CONTROLLER_SECRET || true
|
||||
}
|
||||
|
||||
first_available_proxy_name() {
|
||||
local file="$1"
|
||||
|
||||
@@ -1448,7 +1499,7 @@ resolve_runtime_ports() {
|
||||
local used_ports=""
|
||||
|
||||
preferred_mixed="${MIXED_PORT:-7890}"
|
||||
preferred_controller="${EXTERNAL_CONTROLLER:-127.0.0.1:9090}"
|
||||
preferred_controller="${EXTERNAL_CONTROLLER:-0.0.0.0:9090}"
|
||||
preferred_dns="${CLASH_DNS_PORT:-1053}"
|
||||
|
||||
is_valid_port_number "$preferred_mixed" || die "MIXED_PORT 不合法:$preferred_mixed"
|
||||
@@ -1480,7 +1531,7 @@ mark_install_port_plan() {
|
||||
eval "$resolved"
|
||||
|
||||
preferred_mixed="${MIXED_PORT:-7890}"
|
||||
preferred_controller="${EXTERNAL_CONTROLLER:-127.0.0.1:9090}"
|
||||
preferred_controller="${EXTERNAL_CONTROLLER:-0.0.0.0:9090}"
|
||||
preferred_dns="${CLASH_DNS_PORT:-1053}"
|
||||
|
||||
if [ "$MIXED_PORT_RESOLVED" = "$preferred_mixed" ]; then
|
||||
|
||||
+14
-4
@@ -44,10 +44,12 @@ resolve_yq() {
|
||||
}
|
||||
|
||||
resolve_mihomo() {
|
||||
local arch version file url tmp_file
|
||||
local arch version file url tmp_file url_base custom_url
|
||||
|
||||
arch="$(get_arch)"
|
||||
version="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||
url_base="${MIHOMO_DOWNLOAD_BASE:-https://github.com/MetaCubeX/mihomo/releases/download}"
|
||||
custom_url="${MIHOMO_DOWNLOAD_URL:-}"
|
||||
|
||||
if [ -x "$(mihomo_bin)" ]; then
|
||||
return 0
|
||||
@@ -60,10 +62,14 @@ resolve_mihomo() {
|
||||
*) die "暂不支持的 Mihomo 架构:$arch" ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/MetaCubeX/mihomo/releases/download/${version}/${file}"
|
||||
if [ -n "${custom_url:-}" ]; then
|
||||
url="$custom_url"
|
||||
else
|
||||
url="${url_base%/}/${version}/${file}"
|
||||
fi
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
download_file "$url" "$tmp_file" "mihomo"
|
||||
download_file "$url" "$tmp_file" "mihomo(可在 .env 中设置 MIHOMO_DOWNLOAD_BASE / MIHOMO_DOWNLOAD_URL)"
|
||||
gzip -dc "$tmp_file" > "$(mihomo_bin)"
|
||||
chmod +x "$(mihomo_bin)"
|
||||
rm -f "$tmp_file"
|
||||
@@ -373,8 +379,10 @@ clean_runtime_state() {
|
||||
stop_subconverter || true
|
||||
stop_runtime || true
|
||||
clear_build_meta || true
|
||||
clear_runtime_event_file || true
|
||||
|
||||
rm -f "$RUNTIME_DIR/config.yaml" 2>/dev/null || true
|
||||
rm -f "$(runtime_meta_file)" 2>/dev/null || true
|
||||
rm -f "$RUNTIME_DIR"/*.pid 2>/dev/null || true
|
||||
rm -f "$RUNTIME_DIR"/*.lock 2>/dev/null || true
|
||||
|
||||
@@ -384,6 +392,8 @@ clean_runtime_state() {
|
||||
rm -rf "$RUNTIME_DIR/profiles" 2>/dev/null || true
|
||||
rm -rf "$RUNTIME_DIR/generated" 2>/dev/null || true
|
||||
rm -rf "$RUNTIME_DIR/tmp" 2>/dev/null || true
|
||||
rm -rf "$(runtime_dashboard_dir)" 2>/dev/null || true
|
||||
clear_shell_proxy_persist_state || true
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$BIN_DIR" "$LOG_DIR"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,12 @@ git_remote_name() {
|
||||
}
|
||||
|
||||
sync_runtime_dependencies() {
|
||||
ensure_dashboard_deploy_prerequisites
|
||||
resolve_yq
|
||||
resolve_runtime_kernel
|
||||
resolve_subconverter
|
||||
install_local_dashboard_assets
|
||||
ensure_controller_secret >/dev/null
|
||||
}
|
||||
|
||||
remove_mihomo_binary() {
|
||||
@@ -143,4 +146,4 @@ update_project_code() {
|
||||
fi
|
||||
|
||||
success "更新完成"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -25,6 +25,8 @@ done
|
||||
source "$PROJECT_DIR/scripts/core/common.sh"
|
||||
# shellcheck source=scripts/core/runtime.sh
|
||||
source "$PROJECT_DIR/scripts/core/runtime.sh"
|
||||
# shellcheck source=scripts/core/config.sh
|
||||
source "$PROJECT_DIR/scripts/core/config.sh"
|
||||
# shellcheck source=scripts/init/systemd.sh
|
||||
source "$PROJECT_DIR/scripts/init/systemd.sh"
|
||||
# shellcheck source=scripts/init/systemd-user.sh
|
||||
@@ -40,11 +42,13 @@ service_stop || true
|
||||
remove_runtime_entry || true
|
||||
remove_clashctl_entry || true
|
||||
remove_shell_alias_entry || true
|
||||
clear_shell_proxy_persist_state || true
|
||||
|
||||
if [ "$PURGE_RUNTIME" = "true" ]; then
|
||||
rm -rf "$RUNTIME_DIR"
|
||||
clear_controller_secret || true
|
||||
echo "🗑️ 已删除运行目录:$RUNTIME_DIR"
|
||||
echo "🧩 保留内容:项目目录仍在"
|
||||
echo "🧩 保留内容:项目目录仍在(已清理 controller secret)"
|
||||
elif [ "$DEV_RESET" = "true" ]; then
|
||||
cache_backup_dir="$(mktemp -d)"
|
||||
cache_restore_needed="false"
|
||||
@@ -75,12 +79,13 @@ elif [ "$DEV_RESET" = "true" ]; then
|
||||
fi
|
||||
|
||||
rm -rf "$cache_backup_dir" 2>/dev/null || true
|
||||
clear_controller_secret || true
|
||||
|
||||
echo "🧪 已清理安装状态:$RUNTIME_DIR"
|
||||
echo "🧩 保留内容:subscriptions.yaml、下载缓存与项目目录仍在"
|
||||
echo "🧩 保留内容:subscriptions.yaml、下载缓存与项目目录仍在(已清理 controller secret)"
|
||||
else
|
||||
echo "📦 已卸载安装入口,保留运行目录:$RUNTIME_DIR"
|
||||
echo "🧩 保留内容:runtime 数据仍在"
|
||||
fi
|
||||
|
||||
echo "🟢 卸载完成"
|
||||
echo "🟢 卸载完成"
|
||||
|
||||
Reference in New Issue
Block a user