234 lines
6.8 KiB
Bash
Executable File
234 lines
6.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# shellcheck disable=SC2034
|
|
. "$(dirname "$(dirname "$THIS_SCRIPT_DIR")")/.env"
|
|
|
|
CLASH_RESOURCES_DIR="${CLASH_BASE_DIR}/resources"
|
|
CLASH_CONFIG_BASE="${CLASH_RESOURCES_DIR}/config.yaml"
|
|
CLASH_CONFIG_MIXIN="${CLASH_RESOURCES_DIR}/mixin.yaml"
|
|
CLASH_CONFIG_RUNTIME="${CLASH_RESOURCES_DIR}/runtime.yaml"
|
|
CLASH_CONFIG_TEMP="${CLASH_RESOURCES_DIR}/temp.yaml"
|
|
|
|
BIN_BASE_DIR="${CLASH_BASE_DIR}/bin"
|
|
BIN_KERNEL="${BIN_BASE_DIR}/$KERNEL_NAME"
|
|
BIN_YQ="${BIN_BASE_DIR}/yq"
|
|
BIN_SUBCONVERTER_DIR="${BIN_BASE_DIR}/subconverter"
|
|
BIN_SUBCONVERTER="${BIN_SUBCONVERTER_DIR}/subconverter"
|
|
BIN_SUBCONVERTER_START="$BIN_SUBCONVERTER"
|
|
BIN_SUBCONVERTER_STOP="pkill -9 -f $BIN_SUBCONVERTER"
|
|
BIN_SUBCONVERTER_CONFIG="$BIN_SUBCONVERTER_DIR/pref.yml"
|
|
BIN_SUBCONVERTER_LOG="${BIN_SUBCONVERTER_DIR}/latest.log"
|
|
|
|
CLASH_PROFILES_DIR="${CLASH_RESOURCES_DIR}/profiles"
|
|
CLASH_PROFILES_META="${CLASH_RESOURCES_DIR}/profiles.yaml"
|
|
CLASH_PROFILES_LOG="${CLASH_RESOURCES_DIR}/profiles.log"
|
|
|
|
_is_port_used() {
|
|
local port=$1
|
|
{ ss -tunl 2>/dev/null || netstat -tunl; } | grep -qs ":${port}\b"
|
|
}
|
|
|
|
_get_random_port() {
|
|
local randomPort=$(shuf -i 1024-65535 -n 1)
|
|
! _is_port_used "$randomPort" && { echo "$randomPort" && return; }
|
|
_get_random_port
|
|
}
|
|
|
|
_get_bind_addr() {
|
|
local allowLan bindAddr
|
|
bindAddr=$("$BIN_YQ" '.bind-address // "*"' "$CLASH_CONFIG_RUNTIME")
|
|
allowLan=$("$BIN_YQ" '.allow-lan // false' "$CLASH_CONFIG_RUNTIME")
|
|
|
|
case $allowLan in
|
|
true)
|
|
[ "$bindAddr" = "*" ] && bindAddr=$(_get_local_ip)
|
|
;;
|
|
false)
|
|
bindAddr=127.0.0.1
|
|
;;
|
|
esac
|
|
echo "$bindAddr"
|
|
}
|
|
|
|
_get_local_ip() {
|
|
local local_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}')
|
|
[ -z "$local_ip" ] && local_ip=$(hostname -I | awk '{print $1}')
|
|
echo "$local_ip"
|
|
}
|
|
|
|
function _detect_ext_addr() {
|
|
local ext_addr=$("$BIN_YQ" '.external-controller // ""' "$CLASH_CONFIG_RUNTIME")
|
|
local ext_ip=${ext_addr%%:*}
|
|
EXT_IP=$ext_ip
|
|
EXT_PORT=${ext_addr##*:}
|
|
[ "$ext_ip" = '0.0.0.0' ] && EXT_IP=$(_get_local_ip)
|
|
_is_port_used "$EXT_PORT" && {
|
|
curl -s --noproxy "*" -H "Authorization: Bearer $(_get_secret)" "127.0.0.1:${EXT_PORT}" | grep -qs "${KERNEL_NAME}" && return 0
|
|
local newPort=$(_get_random_port)
|
|
_failcat '🎯' "端口冲突:[external-controller] ${EXT_PORT} 🎲 随机分配 $newPort"
|
|
EXT_PORT=$newPort
|
|
"$BIN_YQ" -i ".external-controller = \"$ext_ip:$newPort\"" "$CLASH_CONFIG_MIXIN"
|
|
_merge_config
|
|
}
|
|
}
|
|
|
|
_color_log() {
|
|
local color="$1"
|
|
local msg="$2"
|
|
|
|
local hex="${color#\#}"
|
|
local r=$((16#${hex:0:2}))
|
|
local g=$((16#${hex:2:2}))
|
|
local b=$((16#${hex:4:2}))
|
|
|
|
local color_code="\033[38;2;${r};${g};${b}m"
|
|
local reset_code="\033[0m"
|
|
|
|
printf "%b%s%b\n" "$color_code" "$msg" "$reset_code"
|
|
}
|
|
|
|
function _okcat() {
|
|
local color=#c8d6e5
|
|
local emoji=😼
|
|
[ $# -gt 1 ] && emoji=$1 && shift
|
|
local msg="${emoji} $1"
|
|
_color_log "$color" "$msg"
|
|
return 0
|
|
}
|
|
|
|
function _failcat() {
|
|
local color=#fd79a8
|
|
local emoji=😾
|
|
[ $# -gt 1 ] && emoji=$1 && shift
|
|
local msg="${emoji} $1"
|
|
_color_log "$color" "$msg" >&2
|
|
return 1
|
|
}
|
|
|
|
function _error_quit() {
|
|
[ $# -gt 0 ] && {
|
|
local color=#f92f60
|
|
local emoji=📢
|
|
[ $# -gt 1 ] && emoji=$1 && shift
|
|
local msg="${emoji} $1"
|
|
_color_log "$color" "$msg"
|
|
}
|
|
exec $SHELL -i
|
|
}
|
|
|
|
function _valid_config() {
|
|
local config="$1"
|
|
[[ ! -e "$config" || "$(wc -l <"$config")" -lt 1 ]] && return 1
|
|
|
|
local test_cmd test_log
|
|
test_cmd=("$BIN_KERNEL" -d "$(dirname "$config")" -f "$config" -t)
|
|
test_log=$("${test_cmd[@]}") || {
|
|
"${test_cmd[@]}"
|
|
grep -qs "unsupport proxy type" <<<"$test_log" && {
|
|
local prefix="检测到订阅中包含不受支持的代理协议"
|
|
[ "$KERNEL_NAME" = "clash" ] && _error_quit "${prefix}, 推荐安装使用 mihomo 内核"
|
|
_error_quit "${prefix}, 请检查并升级内核版本"
|
|
}
|
|
}
|
|
}
|
|
|
|
function _download_config() {
|
|
local dest=$1
|
|
local url=$2
|
|
[ "${url:0:4}" = 'file' ] || _okcat '⏳' '正在下载...'
|
|
_download_raw_config "$dest" "$url" || return 1
|
|
_okcat '🍃' '验证订阅配置...'
|
|
_valid_config "$dest" || {
|
|
_failcat '🍂' "验证失败:尝试订阅转换..."
|
|
cat "$dest" >"${dest}.raw"
|
|
_download_convert_config "$dest" "$url"
|
|
}
|
|
}
|
|
_download_raw_config() {
|
|
local dest=$1
|
|
local url=$2
|
|
|
|
curl \
|
|
--silent \
|
|
--show-error \
|
|
--fail \
|
|
--insecure \
|
|
--location \
|
|
--max-time 5 \
|
|
--retry 1 \
|
|
--user-agent "$CLASH_SUB_UA" \
|
|
--output "$dest" \
|
|
"$url" ||
|
|
wget \
|
|
--no-verbose \
|
|
--no-check-certificate \
|
|
--timeout 5 \
|
|
--tries 1 \
|
|
--user-agent "$CLASH_SUB_UA" \
|
|
--output-document "$dest" \
|
|
"$url"
|
|
}
|
|
_download_convert_config() {
|
|
local dest=$1
|
|
local url=$2
|
|
local flag
|
|
[ "${url:0:4}" = 'file' ] && return 0
|
|
_start_convert
|
|
local convert_url=$(
|
|
target='clash'
|
|
base_url="http://127.0.0.1:${BIN_SUBCONVERTER_PORT}/sub"
|
|
curl \
|
|
--get \
|
|
--silent \
|
|
--show-error \
|
|
--location \
|
|
--output /dev/null \
|
|
--data-urlencode "target=$target" \
|
|
--data-urlencode "url=$url" \
|
|
--write-out '%{url_effective}' \
|
|
"$base_url"
|
|
)
|
|
curl --user-agent "$CLASH_SUB_UA" --silent --output "$dest" "$convert_url"
|
|
flag=$?
|
|
_stop_convert
|
|
return $flag
|
|
}
|
|
|
|
_detect_subconverter_port() {
|
|
BIN_SUBCONVERTER_PORT=$("$BIN_YQ" '.server.port' "$BIN_SUBCONVERTER_CONFIG")
|
|
_is_port_used "$BIN_SUBCONVERTER_PORT" && {
|
|
local newPort=$(_get_random_port)
|
|
_failcat '🎯' "端口冲突:[subconverter] ${BIN_SUBCONVERTER_PORT} 🎲 随机分配:$newPort"
|
|
BIN_SUBCONVERTER_PORT=$newPort
|
|
"$BIN_YQ" -i ".server.port = $newPort" "$BIN_SUBCONVERTER_CONFIG" 2>/dev/null
|
|
}
|
|
}
|
|
|
|
_start_convert() {
|
|
_detect_subconverter_port
|
|
local check_cmd="curl http://localhost:${BIN_SUBCONVERTER_PORT}/version"
|
|
$check_cmd >&/dev/null && return 0
|
|
("$BIN_SUBCONVERTER_START" >&"$BIN_SUBCONVERTER_LOG" &)
|
|
local start=$(date +%s)
|
|
while ! $check_cmd >&/dev/null; do
|
|
sleep 0.5s
|
|
local now=$(date +%s)
|
|
[ $((now - start)) -gt 2 ] && _error_quit "订阅转换服务未启动,请检查日志:$BIN_SUBCONVERTER_LOG"
|
|
done
|
|
}
|
|
_stop_convert() {
|
|
$BIN_SUBCONVERTER_STOP >/dev/null
|
|
}
|
|
|
|
_set_env() {
|
|
local key=$1
|
|
local value=$2
|
|
local env_path="${CLASH_BASE_DIR}/.env"
|
|
|
|
grep -qE "^${key}=" "$env_path" && {
|
|
value=${value//&/\\&}
|
|
sed -i "s|^${key}=.*|${key}=${value}|" "$env_path"
|
|
return $?
|
|
}
|
|
echo "${key}=${value}" >>"$env_path"
|
|
}
|