mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 20:23:04 +08:00
refactor: rebuild project architecture and codebase
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_clashctl_real() {
|
||||
if command -v clashctl-bin >/dev/null 2>&1; then
|
||||
clashctl-bin "$@"
|
||||
return $?
|
||||
fi
|
||||
|
||||
command clashctl "$@"
|
||||
}
|
||||
|
||||
_clash_alias_print_sep() {
|
||||
echo
|
||||
}
|
||||
|
||||
_clash_alias_proxy_on() {
|
||||
eval "$(_clashctl_real proxy on)" || return $?
|
||||
}
|
||||
|
||||
_clash_alias_proxy_off() {
|
||||
eval "$(_clashctl_real proxy off)" || true
|
||||
}
|
||||
|
||||
_clash_alias_proxy_show() {
|
||||
_clashctl_real proxy show 2>/dev/null || true
|
||||
}
|
||||
|
||||
_clash_alias_status_next() {
|
||||
_clashctl_real status-next 2>/dev/null || echo "clashctl status"
|
||||
}
|
||||
|
||||
_clash_alias_prepare_on() {
|
||||
# 这里不直接自己做 regenerate / restart / fallback,
|
||||
# 而是统一交给 clashctl on 主链处理,避免双执行链。
|
||||
# shell 层只负责“闭环体验”,不抢 runtime / build 的职责。
|
||||
return 0
|
||||
}
|
||||
|
||||
_clash_alias_after_on() {
|
||||
_clash_alias_proxy_on || return $?
|
||||
|
||||
_clash_alias_print_sep
|
||||
_clash_alias_proxy_show
|
||||
|
||||
echo "👉 下一步:$(_clash_alias_status_next)"
|
||||
}
|
||||
|
||||
_clash_alias_after_off() {
|
||||
_clash_alias_print_sep
|
||||
echo "🔴 已关闭代理环境"
|
||||
echo "🧹 当前 Shell 代理变量已清理"
|
||||
echo "👉 下一步:clashctl status"
|
||||
}
|
||||
|
||||
_clash_alias_run_on() {
|
||||
_clash_alias_prepare_on || return $?
|
||||
|
||||
_clashctl_real on "$@" || return $?
|
||||
|
||||
_clash_alias_after_on || return $?
|
||||
}
|
||||
|
||||
_clash_alias_run_off() {
|
||||
_clash_alias_proxy_off
|
||||
_clashctl_real off "$@" || return $?
|
||||
}
|
||||
|
||||
clashctl() {
|
||||
case "${1:-}" in
|
||||
on)
|
||||
shift || true
|
||||
_clash_alias_run_on "$@"
|
||||
;;
|
||||
off)
|
||||
shift || true
|
||||
_clash_alias_run_off "$@"
|
||||
;;
|
||||
proxy)
|
||||
case "${2:-}" in
|
||||
on)
|
||||
_clash_alias_proxy_on || return $?
|
||||
_clash_alias_print_sep
|
||||
_clash_alias_proxy_show
|
||||
;;
|
||||
off)
|
||||
_clash_alias_proxy_off
|
||||
_clash_alias_print_sep
|
||||
echo "🧹 当前 Shell 代理变量已清理"
|
||||
;;
|
||||
*)
|
||||
_clashctl_real "$@"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
ui)
|
||||
shift || true
|
||||
# ui 前如果 runtime 已运行但当前 shell 没代理,不强制注入;
|
||||
# 保持 UI 行为纯粹,只走原命令。
|
||||
_clashctl_real ui "$@"
|
||||
;;
|
||||
status)
|
||||
shift || true
|
||||
_clashctl_real status "$@"
|
||||
;;
|
||||
*)
|
||||
_clashctl_real "$@"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 快捷入口全部收敛到 clashctl 函数
|
||||
clashon() {
|
||||
clashctl on "$@" || return $?
|
||||
}
|
||||
|
||||
clashoff() {
|
||||
clashctl off "$@" || return $?
|
||||
}
|
||||
|
||||
clashproxy() {
|
||||
case "${1:-show}" in
|
||||
on)
|
||||
clashctl proxy on
|
||||
;;
|
||||
off)
|
||||
clashctl proxy off
|
||||
;;
|
||||
show|status)
|
||||
clashctl proxy show
|
||||
;;
|
||||
groups)
|
||||
clashctl proxy groups
|
||||
;;
|
||||
current)
|
||||
shift || true
|
||||
clashctl proxy current "$@"
|
||||
;;
|
||||
nodes)
|
||||
shift || true
|
||||
clashctl proxy nodes "$@"
|
||||
;;
|
||||
select)
|
||||
shift || true
|
||||
clashctl proxy select "$@"
|
||||
;;
|
||||
*)
|
||||
echo "🧭 用法:clashproxy [show|on|off|groups|current|nodes|select]"
|
||||
echo "💡 主路径切节点请使用:clashselect 或 clashctl select"
|
||||
return 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
clashls() {
|
||||
clashctl ls "$@"
|
||||
}
|
||||
|
||||
clashselect() {
|
||||
clashctl select "$@"
|
||||
}
|
||||
|
||||
clashui() {
|
||||
clashctl ui "$@"
|
||||
}
|
||||
|
||||
clashsecret() {
|
||||
clashctl secret "$@"
|
||||
}
|
||||
|
||||
clashtun() {
|
||||
clashctl tun "$@"
|
||||
}
|
||||
|
||||
clashupgrade() {
|
||||
clashctl upgrade "$@"
|
||||
}
|
||||
|
||||
clashmixin() {
|
||||
case "${1:-}" in
|
||||
-e|--edit)
|
||||
clashctl mixin edit
|
||||
;;
|
||||
-c|--raw)
|
||||
clashctl mixin raw
|
||||
;;
|
||||
-r|--runtime)
|
||||
clashctl mixin runtime
|
||||
;;
|
||||
"")
|
||||
clashctl mixin
|
||||
;;
|
||||
*)
|
||||
clashctl mixin "$@"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# shell 被 source 时不自动执行任何代理动作
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,273 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=scripts/core/common.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/common.sh"
|
||||
|
||||
proxy_host() {
|
||||
echo "${CLASH_PROXY_HOST:-127.0.0.1}"
|
||||
}
|
||||
|
||||
proxy_port() {
|
||||
local config_file="${1:-$RUNTIME_DIR/config.yaml}"
|
||||
local port
|
||||
|
||||
[ -s "$config_file" ] || die "配置文件不存在:$config_file"
|
||||
|
||||
port="$("$(yq_bin)" eval '.["mixed-port"] // .port // ""' "$config_file" 2>/dev/null | head -n 1)"
|
||||
[ -n "${port:-}" ] && [ "$port" != "null" ] || die "未在配置文件中找到代理端口"
|
||||
|
||||
echo "$port"
|
||||
}
|
||||
|
||||
proxy_http_url() {
|
||||
echo "http://$(proxy_host):$(proxy_port)"
|
||||
}
|
||||
|
||||
proxy_socks_url() {
|
||||
echo "socks5://$(proxy_host):$(proxy_port)"
|
||||
}
|
||||
|
||||
proxy_no_proxy_value() {
|
||||
echo "${NO_PROXY_DEFAULT:-127.0.0.1,localhost,::1}"
|
||||
}
|
||||
|
||||
print_proxy_show() {
|
||||
echo
|
||||
echo "😼 当前代理环境"
|
||||
echo
|
||||
echo "🌐 HTTP:$(proxy_http_url)"
|
||||
echo "🧦 SOCKS5:$(proxy_socks_url)"
|
||||
echo "🚫 NO_PROXY:$(proxy_no_proxy_value)"
|
||||
echo
|
||||
}
|
||||
|
||||
print_proxy_on_script() {
|
||||
local http_url socks_url no_proxy
|
||||
|
||||
http_url="$(proxy_http_url)"
|
||||
socks_url="$(proxy_socks_url)"
|
||||
no_proxy="$(proxy_no_proxy_value)"
|
||||
|
||||
cat <<EOF
|
||||
export http_proxy="$http_url"
|
||||
export https_proxy="$http_url"
|
||||
export HTTP_PROXY="$http_url"
|
||||
export HTTPS_PROXY="$http_url"
|
||||
export all_proxy="$socks_url"
|
||||
export ALL_PROXY="$socks_url"
|
||||
export no_proxy="$no_proxy"
|
||||
export NO_PROXY="$no_proxy"
|
||||
EOF
|
||||
}
|
||||
|
||||
print_proxy_off_script() {
|
||||
cat <<'EOF'
|
||||
unset http_proxy
|
||||
unset https_proxy
|
||||
unset HTTP_PROXY
|
||||
unset HTTPS_PROXY
|
||||
unset all_proxy
|
||||
unset ALL_PROXY
|
||||
unset no_proxy
|
||||
unset NO_PROXY
|
||||
EOF
|
||||
}
|
||||
|
||||
controller_addr() {
|
||||
local config_file="${1:-$RUNTIME_DIR/config.yaml}"
|
||||
|
||||
[ -s "$config_file" ] || die "配置文件不存在:$config_file"
|
||||
|
||||
"$(yq_bin)" eval '.["external-controller"] // ""' "$config_file" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
controller_secret() {
|
||||
local config_file="${1:-$RUNTIME_DIR/config.yaml}"
|
||||
|
||||
[ -s "$config_file" ] || die "配置文件不存在:$config_file"
|
||||
|
||||
"$(yq_bin)" eval '.secret // ""' "$config_file" 2>/dev/null | head -n 1
|
||||
}
|
||||
|
||||
controller_api_base() {
|
||||
local addr
|
||||
|
||||
addr="$(controller_addr)"
|
||||
[ -n "${addr:-}" ] || die "未找到 external-controller"
|
||||
[ "$addr" != "null" ] || die "未找到 external-controller"
|
||||
|
||||
echo "http://$addr"
|
||||
}
|
||||
|
||||
controller_curl() {
|
||||
local method="$1"
|
||||
local path="$2"
|
||||
local data="${3:-}"
|
||||
local base secret
|
||||
|
||||
base="$(controller_api_base)"
|
||||
secret="$(controller_secret)"
|
||||
|
||||
if [ -n "${data:-}" ]; then
|
||||
curl -fsSL -X "$method" \
|
||||
-H "Content-Type: application/json" \
|
||||
${secret:+-H "Authorization: Bearer $secret"} \
|
||||
--data "$data" \
|
||||
"$base$path"
|
||||
else
|
||||
curl -fsSL -X "$method" \
|
||||
${secret:+-H "Authorization: Bearer $secret"} \
|
||||
"$base$path"
|
||||
fi
|
||||
}
|
||||
|
||||
proxy_controller_reachable() {
|
||||
controller_curl GET "/version" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
proxy_groups_json() {
|
||||
controller_curl GET "/proxies"
|
||||
}
|
||||
|
||||
proxy_group_exists() {
|
||||
local group="$1"
|
||||
|
||||
[ -n "${group:-}" ] || return 1
|
||||
|
||||
[ "$(proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies | has(\"$group\")" - 2>/dev/null)" = "true" ]
|
||||
}
|
||||
|
||||
proxy_group_type() {
|
||||
local group="$1"
|
||||
|
||||
[ -n "${group:-}" ] || die "策略组名称不能为空"
|
||||
proxy_group_exists "$group" || die "策略组不存在:$group"
|
||||
|
||||
proxy_groups_json | "$(yq_bin)" -p=json eval ".proxies.\"$group\".type // \"\"" - 2>/dev/null
|
||||
}
|
||||
|
||||
proxy_group_is_selector() {
|
||||
local type
|
||||
|
||||
type="$(proxy_group_type "$1")"
|
||||
|
||||
case "$type" in
|
||||
Selector|URLTest|Fallback|LoadBalance)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
proxy_group_list() {
|
||||
proxy_groups_json \
|
||||
| "$(yq_bin)" -p=json eval '
|
||||
.proxies
|
||||
| to_entries
|
||||
| map(select(.value.all != null))
|
||||
| .[].key
|
||||
' - 2>/dev/null
|
||||
}
|
||||
|
||||
proxy_group_current() {
|
||||
local group="$1"
|
||||
|
||||
[ -n "${group:-}" ] || die "策略组名称不能为空"
|
||||
proxy_group_exists "$group" || die "策略组不存在:$group"
|
||||
|
||||
proxy_groups_json \
|
||||
| "$(yq_bin)" -p=json eval ".proxies.\"$group\".now // \"\"" - 2>/dev/null
|
||||
}
|
||||
|
||||
proxy_group_nodes() {
|
||||
local group="$1"
|
||||
|
||||
[ -n "${group:-}" ] || die "策略组名称不能为空"
|
||||
proxy_group_exists "$group" || die "策略组不存在:$group"
|
||||
|
||||
proxy_groups_json \
|
||||
| "$(yq_bin)" -p=json eval ".proxies.\"$group\".all[] // \"\"" - 2>/dev/null
|
||||
}
|
||||
|
||||
proxy_group_select() {
|
||||
local group="$1"
|
||||
local node="$2"
|
||||
|
||||
[ -n "${group:-}" ] || die "策略组名称不能为空"
|
||||
[ -n "${node:-}" ] || die "节点名称不能为空"
|
||||
|
||||
proxy_group_exists "$group" || die "策略组不存在:$group"
|
||||
proxy_group_is_selector "$group" || die "该策略组不支持手动切换:$group"
|
||||
|
||||
if ! proxy_group_nodes "$group" | grep -Fxq "$node"; then
|
||||
die "节点不存在于策略组中:$group -> $node"
|
||||
fi
|
||||
|
||||
controller_curl PUT "/proxies/$group" "{\"name\":\"$node\"}" >/dev/null
|
||||
}
|
||||
|
||||
proxy_group_count() {
|
||||
proxy_group_list 2>/dev/null | awk 'NF{c++} END{print c+0}'
|
||||
}
|
||||
|
||||
default_proxy_group_name() {
|
||||
local config_file="${1:-$RUNTIME_DIR/config.yaml}"
|
||||
local group_name
|
||||
|
||||
[ -s "$config_file" ] || return 1
|
||||
|
||||
group_name="$("$(yq_bin)" eval '
|
||||
.["proxy-groups"][] |
|
||||
select(
|
||||
(.type == "select") or
|
||||
(.type == "url-test") or
|
||||
(.type == "fallback") or
|
||||
(.type == "load-balance")
|
||||
) |
|
||||
.name
|
||||
' "$config_file" 2>/dev/null | head -n 1)"
|
||||
|
||||
[ -n "${group_name:-}" ] || return 1
|
||||
echo "$group_name"
|
||||
}
|
||||
|
||||
default_proxy_group_current() {
|
||||
local group
|
||||
|
||||
group="$(default_proxy_group_name 2>/dev/null || true)"
|
||||
[ -n "${group:-}" ] || return 1
|
||||
|
||||
proxy_group_current "$group" 2>/dev/null || return 1
|
||||
}
|
||||
|
||||
print_proxy_groups_status() {
|
||||
local group current
|
||||
|
||||
while IFS= read -r group; do
|
||||
[ -n "${group:-}" ] || continue
|
||||
current="$(proxy_group_current "$group" 2>/dev/null || true)"
|
||||
|
||||
if [ -n "${current:-}" ]; then
|
||||
echo "$group -> $current"
|
||||
else
|
||||
echo "$group -> <unknown>"
|
||||
fi
|
||||
done < <(proxy_group_list)
|
||||
}
|
||||
|
||||
print_proxy_groups_summary() {
|
||||
local group current
|
||||
|
||||
while IFS= read -r group; do
|
||||
[ -n "${group:-}" ] || continue
|
||||
current="$(proxy_group_current "$group" 2>/dev/null || true)"
|
||||
|
||||
if [ -n "${current:-}" ]; then
|
||||
echo "$group -> $current"
|
||||
else
|
||||
echo "$group"
|
||||
fi
|
||||
done < <(proxy_group_list)
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=scripts/core/common.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/common.sh"
|
||||
# shellcheck source=scripts/core/config.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/config.sh"
|
||||
|
||||
resolve_yq() {
|
||||
local arch version file url tmp_dir tmp_file
|
||||
|
||||
arch="$(get_arch)"
|
||||
version="${YQ_VERSION:-$DEFAULT_YQ_VERSION}"
|
||||
|
||||
if [ -x "$(yq_bin)" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$arch" in
|
||||
amd64) file="yq_linux_amd64.tar.gz" ;;
|
||||
arm64) file="yq_linux_arm64.tar.gz" ;;
|
||||
armv7) file="yq_linux_arm.tar.gz" ;;
|
||||
*) die "暂不支持的 yq 架构:$arch" ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/mikefarah/yq/releases/download/${version}/${file}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
tmp_file="$tmp_dir/$file"
|
||||
|
||||
download_file "$url" "$tmp_file" "yq"
|
||||
tar -xzf "$tmp_file" -C "$tmp_dir"
|
||||
|
||||
if [ -f "$tmp_dir/yq_linux_${arch}" ]; then
|
||||
install -m 0755 "$tmp_dir/yq_linux_${arch}" "$(yq_bin)"
|
||||
elif [ -f "$tmp_dir/yq_linux_arm" ]; then
|
||||
install -m 0755 "$tmp_dir/yq_linux_arm" "$(yq_bin)"
|
||||
elif [ -f "$tmp_dir/yq" ]; then
|
||||
install -m 0755 "$tmp_dir/yq" "$(yq_bin)"
|
||||
else
|
||||
rm -rf "$tmp_dir"
|
||||
die "解压后未找到 yq 可执行文件"
|
||||
fi
|
||||
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
|
||||
resolve_mihomo() {
|
||||
local arch version file url tmp_file
|
||||
|
||||
arch="$(get_arch)"
|
||||
version="${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||
|
||||
if [ -x "$(mihomo_bin)" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$arch" in
|
||||
amd64) file="mihomo-linux-amd64-compatible-${version}.gz" ;;
|
||||
arm64) file="mihomo-linux-arm64-${version}.gz" ;;
|
||||
armv7) file="mihomo-linux-armv7-${version}.gz" ;;
|
||||
*) die "暂不支持的 Mihomo 架构:$arch" ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/MetaCubeX/mihomo/releases/download/${version}/${file}"
|
||||
tmp_file="$(mktemp)"
|
||||
|
||||
download_file "$url" "$tmp_file" "mihomo"
|
||||
gzip -dc "$tmp_file" > "$(mihomo_bin)"
|
||||
chmod +x "$(mihomo_bin)"
|
||||
rm -f "$tmp_file"
|
||||
}
|
||||
|
||||
resolve_clash() {
|
||||
local arch version version_no_v url_base tmp_file url downloaded="false"
|
||||
local candidates file
|
||||
|
||||
arch="$(get_arch)"
|
||||
version="${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||
version_no_v="${version#v}"
|
||||
url_base="${CLASH_DOWNLOAD_BASE:-https://github.com/WindSpiritSR/clash/releases/download}"
|
||||
|
||||
if [ -x "$(clash_bin)" ] \
|
||||
&& [ "$(read_runtime_value "KERNEL_TYPE_INSTALLED" 2>/dev/null || true)" = "clash" ] \
|
||||
&& [ "$(read_runtime_value "CLASH_VERSION_INSTALLED" 2>/dev/null || true)" = "$version" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$arch" in
|
||||
amd64)
|
||||
candidates="
|
||||
clash-linux-amd64-${version}.gz
|
||||
clash-linux-amd64-v${version_no_v}.gz
|
||||
"
|
||||
;;
|
||||
arm64)
|
||||
candidates="
|
||||
clash-linux-arm64-${version}.gz
|
||||
clash-linux-arm64-v${version_no_v}.gz
|
||||
clash-linux-armv8-${version}.gz
|
||||
clash-linux-armv8-v${version_no_v}.gz
|
||||
"
|
||||
;;
|
||||
armv7)
|
||||
candidates="
|
||||
clash-linux-armv7-${version}.gz
|
||||
clash-linux-armv7-v${version_no_v}.gz
|
||||
"
|
||||
;;
|
||||
*)
|
||||
die "暂不支持的 Clash 架构:$arch"
|
||||
;;
|
||||
esac
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
rm -f "$tmp_file"
|
||||
|
||||
for file in $candidates; do
|
||||
url="${url_base}/${version}/${file}"
|
||||
|
||||
if download_file "$url" "$tmp_file" "clash"; then
|
||||
downloaded="true"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
[ "$downloaded" = "true" ] || {
|
||||
rm -f "$tmp_file"
|
||||
die "Clash 内核下载失败,请检查版本或在 .env 中覆盖 CLASH_DOWNLOAD_BASE / CLASH_VERSION"
|
||||
}
|
||||
|
||||
gzip -dc "$tmp_file" > "$(clash_bin)"
|
||||
chmod +x "$(clash_bin)"
|
||||
rm -f "$tmp_file"
|
||||
|
||||
write_runtime_value "KERNEL_TYPE_INSTALLED" "clash"
|
||||
write_runtime_value "CLASH_VERSION_INSTALLED" "$version"
|
||||
}
|
||||
|
||||
resolve_runtime_kernel() {
|
||||
case "$(runtime_kernel_type)" in
|
||||
mihomo)
|
||||
resolve_mihomo
|
||||
write_runtime_value "KERNEL_TYPE_INSTALLED" "mihomo"
|
||||
write_runtime_value "MIHOMO_VERSION_INSTALLED" "${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||
;;
|
||||
clash)
|
||||
resolve_clash
|
||||
;;
|
||||
*)
|
||||
die "未知内核类型:$(runtime_kernel_type)"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
subconverter_version_file_value() {
|
||||
read_runtime_value "SUBCONVERTER_VERSION_INSTALLED" 2>/dev/null || true
|
||||
}
|
||||
|
||||
mark_subconverter_version_installed() {
|
||||
local version="$1"
|
||||
write_runtime_value "SUBCONVERTER_VERSION_INSTALLED" "$version"
|
||||
}
|
||||
|
||||
resolve_subconverter() {
|
||||
local arch version file url tmp_dir tmp_file target_dir installed_version
|
||||
|
||||
arch="$(get_arch)"
|
||||
version="${SUBCONVERTER_VERSION:-$DEFAULT_SUBCONVERTER_VERSION}"
|
||||
target_dir="$(subconverter_home)"
|
||||
installed_version="$(subconverter_version_file_value)"
|
||||
|
||||
if [ -x "$(subconverter_bin)" ] && [ "$installed_version" = "$version" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$arch" in
|
||||
amd64) file="subconverter_linux64.tar.gz" ;;
|
||||
arm64) file="subconverter_aarch64.tar.gz" ;;
|
||||
armv7) file="subconverter_armv7.tar.gz" ;;
|
||||
*) die "暂不支持的 subconverter 架构:$arch" ;;
|
||||
esac
|
||||
|
||||
url="https://github.com/tindy2013/subconverter/releases/download/${version}/${file}"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
tmp_file="$tmp_dir/$file"
|
||||
|
||||
download_file "$url" "$tmp_file" "subconverter"
|
||||
|
||||
rm -rf "$target_dir"
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
tar -xzf "$tmp_file" -C "$target_dir"
|
||||
|
||||
[ -x "$(subconverter_bin)" ] || {
|
||||
rm -rf "$tmp_dir"
|
||||
die "解压后未找到 subconverter 可执行文件"
|
||||
}
|
||||
|
||||
chmod +x "$(subconverter_bin)"
|
||||
mark_subconverter_version_installed "$version"
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
|
||||
ensure_runtime_config_ready() {
|
||||
local config_file last_file now reason
|
||||
|
||||
config_file="$RUNTIME_DIR/config.yaml"
|
||||
last_file="$RUNTIME_DIR/config.last.yaml"
|
||||
|
||||
if [ -s "$config_file" ] && test_runtime_config "$config_file" >/dev/null 2>&1; then
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_USED" "false"
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_TIME" ""
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_REASON" ""
|
||||
write_runtime_event_value "RUNTIME_LAST_RISK_LEVEL" "$(calculate_runtime_risk_level)"
|
||||
mark_runtime_config_source "runtime"
|
||||
return 0
|
||||
fi
|
||||
|
||||
reason="当前运行配置不可用"
|
||||
warn "当前运行配置不可用,尝试回退到上一次成功配置"
|
||||
|
||||
[ -s "$last_file" ] || die "当前配置不可用,且不存在可回退的最后成功配置:$last_file"
|
||||
|
||||
if test_runtime_config "$last_file" >/dev/null 2>&1; then
|
||||
cp -f "$last_file" "$config_file"
|
||||
now="$(now_datetime)"
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_USED" "true"
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_TIME" "$now"
|
||||
write_runtime_event_value "RUNTIME_LAST_FALLBACK_REASON" "$reason"
|
||||
write_runtime_event_value "RUNTIME_LAST_RISK_LEVEL" "$(calculate_runtime_risk_level)"
|
||||
mark_runtime_config_source "last_good"
|
||||
success "已回退到最后成功配置"
|
||||
return 0
|
||||
fi
|
||||
|
||||
die "最后成功配置也不可用:$last_file"
|
||||
}
|
||||
|
||||
tun_effective_check() {
|
||||
local config_tun_enabled auto_route controller_ok route_ok
|
||||
|
||||
config_tun_enabled="$(runtime_config_tun_enabled 2>/dev/null || true)"
|
||||
|
||||
if [ "$(tun_enabled)" != "true" ]; then
|
||||
echo "disabled-in-state"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${config_tun_enabled:-false}" != "true" ]; then
|
||||
echo "disabled-in-runtime-config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! status_is_running 2>/dev/null; then
|
||||
echo "runtime-not-running"
|
||||
return 1
|
||||
fi
|
||||
|
||||
controller_ok="false"
|
||||
if proxy_controller_reachable 2>/dev/null; then
|
||||
controller_ok="true"
|
||||
fi
|
||||
|
||||
if [ "$controller_ok" != "true" ]; then
|
||||
echo "controller-unreachable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
auto_route="$(runtime_config_tun_auto_route 2>/dev/null || true)"
|
||||
route_ok="unknown"
|
||||
|
||||
if [ "${auto_route:-false}" = "true" ]; then
|
||||
if default_route_is_tun_like; then
|
||||
route_ok="true"
|
||||
else
|
||||
route_ok="false"
|
||||
fi
|
||||
|
||||
if [ "$route_ok" != "true" ]; then
|
||||
echo "default-route-not-tun"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
return 0
|
||||
}
|
||||
|
||||
tun_disable_check() {
|
||||
local config_tun_enabled
|
||||
|
||||
config_tun_enabled="$(runtime_config_tun_enabled 2>/dev/null || true)"
|
||||
|
||||
if [ "$(tun_enabled)" = "true" ]; then
|
||||
echo "state-still-enabled"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "${config_tun_enabled:-false}" = "true" ]; then
|
||||
echo "runtime-config-still-enabled"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if status_is_running 2>/dev/null; then
|
||||
if ! proxy_controller_reachable 2>/dev/null; then
|
||||
echo "runtime-restarted-but-controller-unreachable"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
return 0
|
||||
}
|
||||
|
||||
start_runtime() {
|
||||
local config_file="$RUNTIME_DIR/config.yaml"
|
||||
|
||||
resolve_runtime_kernel
|
||||
ensure_runtime_config_ready
|
||||
|
||||
if [ -f "$RUNTIME_DIR/mihomo.pid" ]; then
|
||||
local old_pid
|
||||
old_pid="$(cat "$RUNTIME_DIR/mihomo.pid" 2>/dev/null || true)"
|
||||
if [ -n "${old_pid:-}" ] && kill -0 "$old_pid" 2>/dev/null; then
|
||||
warn "Mihomo 已在运行:pid=$old_pid"
|
||||
return 0
|
||||
fi
|
||||
rm -f "$RUNTIME_DIR/mihomo.pid"
|
||||
fi
|
||||
|
||||
nohup "$(runtime_kernel_bin)" -f "$config_file" -d "$RUNTIME_DIR" \
|
||||
> "$LOG_DIR/mihomo.out.log" 2>&1 &
|
||||
|
||||
echo $! > "$RUNTIME_DIR/mihomo.pid"
|
||||
success "$(runtime_kernel_name) 已启动:pid=$(cat "$RUNTIME_DIR/mihomo.pid")"
|
||||
}
|
||||
|
||||
stop_runtime() {
|
||||
local pid_file="$RUNTIME_DIR/mihomo.pid"
|
||||
|
||||
[ -f "$pid_file" ] || {
|
||||
warn "Mihomo 当前未运行"
|
||||
return 0
|
||||
}
|
||||
|
||||
local pid
|
||||
pid="$(cat "$pid_file" 2>/dev/null || true)"
|
||||
|
||||
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$pid_file"
|
||||
success "Mihomo 已停止"
|
||||
}
|
||||
|
||||
runtime_status_text() {
|
||||
if [ -f "$RUNTIME_DIR/mihomo.pid" ]; then
|
||||
local pid
|
||||
pid="$(cat "$RUNTIME_DIR/mihomo.pid" 2>/dev/null || true)"
|
||||
if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
echo "运行中"
|
||||
echo "进程号:$pid"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "未运行"
|
||||
}
|
||||
|
||||
clean_runtime_state() {
|
||||
stop_subconverter || true
|
||||
stop_runtime || true
|
||||
clear_build_meta || true
|
||||
|
||||
rm -f "$RUNTIME_DIR/config.yaml" 2>/dev/null || true
|
||||
rm -f "$RUNTIME_DIR"/*.pid 2>/dev/null || true
|
||||
rm -f "$RUNTIME_DIR"/*.lock 2>/dev/null || true
|
||||
|
||||
rm -rf "$LOG_DIR" 2>/dev/null || true
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
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
|
||||
|
||||
mkdir -p "$RUNTIME_DIR" "$BIN_DIR" "$LOG_DIR"
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck source=scripts/core/common.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/common.sh"
|
||||
# shellcheck source=scripts/core/runtime.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/runtime.sh"
|
||||
# shellcheck source=scripts/core/config.sh
|
||||
source "${PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}/scripts/core/config.sh"
|
||||
|
||||
ensure_git_ready() {
|
||||
command -v git >/dev/null 2>&1 || die "当前系统缺少 git"
|
||||
|
||||
git -C "$PROJECT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||
|| die "当前目录不是 Git 仓库:$PROJECT_DIR"
|
||||
}
|
||||
|
||||
current_git_branch() {
|
||||
git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null
|
||||
}
|
||||
|
||||
update_branch() {
|
||||
local branch
|
||||
|
||||
branch="$(read_env_value "CLASH_UPDATE_BRANCH" 2>/dev/null || true)"
|
||||
if [ -n "${branch:-}" ]; then
|
||||
echo "$branch"
|
||||
return 0
|
||||
fi
|
||||
|
||||
current_git_branch
|
||||
}
|
||||
|
||||
git_has_local_changes() {
|
||||
[ -n "$(git -C "$PROJECT_DIR" status --porcelain --untracked-files=no 2>/dev/null)" ]
|
||||
}
|
||||
|
||||
git_remote_name() {
|
||||
echo "${CLASH_GIT_REMOTE:-origin}"
|
||||
}
|
||||
|
||||
sync_runtime_dependencies() {
|
||||
resolve_yq
|
||||
resolve_runtime_kernel
|
||||
resolve_subconverter
|
||||
}
|
||||
|
||||
remove_mihomo_binary() {
|
||||
rm -f "$(mihomo_bin)" 2>/dev/null || true
|
||||
}
|
||||
|
||||
remove_clash_binary() {
|
||||
rm -f "$(clash_bin)" 2>/dev/null || true
|
||||
}
|
||||
|
||||
upgrade_runtime_kernel() {
|
||||
local target_kernel="${1:-}"
|
||||
local verbose="${2:-false}"
|
||||
local was_running="false"
|
||||
|
||||
target_kernel="$(normalize_kernel_type "${target_kernel:-$(runtime_kernel_type)}")"
|
||||
|
||||
if status_is_running 2>/dev/null; then
|
||||
was_running="true"
|
||||
fi
|
||||
|
||||
write_runtime_kernel_type "$target_kernel"
|
||||
|
||||
if [ "$verbose" = "true" ]; then
|
||||
info "当前架构:$(get_arch)"
|
||||
info "目标内核:$target_kernel"
|
||||
case "$target_kernel" in
|
||||
mihomo)
|
||||
info "目标版本:${MIHOMO_VERSION:-$DEFAULT_MIHOMO_VERSION}"
|
||||
;;
|
||||
clash)
|
||||
info "目标版本:${CLASH_VERSION:-$DEFAULT_CLASH_VERSION}"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$target_kernel" in
|
||||
mihomo)
|
||||
info "正在升级 mihomo 内核"
|
||||
remove_mihomo_binary
|
||||
resolve_mihomo
|
||||
[ -x "$(mihomo_bin)" ] || die "mihomo 内核升级失败"
|
||||
;;
|
||||
clash)
|
||||
info "正在升级 clash 内核"
|
||||
remove_clash_binary
|
||||
resolve_clash
|
||||
[ -x "$(clash_bin)" ] || die "clash 内核升级失败"
|
||||
;;
|
||||
*)
|
||||
die "未知内核类型:$target_kernel"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$was_running" = "true" ]; then
|
||||
info "检测到当前内核正在运行,正在重启服务"
|
||||
service_restart
|
||||
success "内核升级成功,已重启生效"
|
||||
else
|
||||
success "内核升级成功"
|
||||
fi
|
||||
}
|
||||
|
||||
update_project_code() {
|
||||
local force_mode="$1"
|
||||
local regenerate_mode="$2"
|
||||
local remote_name branch
|
||||
|
||||
ensure_git_ready
|
||||
|
||||
remote_name="$(git_remote_name)"
|
||||
branch="$(update_branch)"
|
||||
|
||||
[ -n "${branch:-}" ] || die "无法识别当前分支"
|
||||
|
||||
info "远程仓库:$remote_name"
|
||||
info "更新分支:$branch"
|
||||
|
||||
if git_has_local_changes && [ "$force_mode" != "true" ]; then
|
||||
die "检测到本地有未提交改动,请先提交,或使用 clashctl update --force"
|
||||
fi
|
||||
|
||||
info "正在获取最新代码"
|
||||
git -C "$PROJECT_DIR" fetch "$remote_name" "$branch" || die "获取远程代码失败"
|
||||
|
||||
if git_has_local_changes && [ "$force_mode" = "true" ]; then
|
||||
warn "检测到本地改动,正在强制覆盖"
|
||||
git -C "$PROJECT_DIR" reset --hard "$remote_name/$branch" || die "强制覆盖失败"
|
||||
else
|
||||
git -C "$PROJECT_DIR" reset --hard "$remote_name/$branch" || die "更新失败"
|
||||
fi
|
||||
|
||||
success "代码已更新到最新版本"
|
||||
|
||||
sync_runtime_dependencies
|
||||
|
||||
if [ "$regenerate_mode" = "true" ]; then
|
||||
regenerate_config
|
||||
fi
|
||||
|
||||
success "更新完成"
|
||||
}
|
||||
Reference in New Issue
Block a user