common.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. # shellcheck disable=SC2148
  2. # shellcheck disable=SC2034
  3. # shellcheck disable=SC2155
  4. [ -n "$BASH_VERSION" ] && set +o noglob
  5. [ -n "$ZSH_VERSION" ] && setopt glob no_nomatch
  6. URL_GH_PROXY='https://gh-proxy.com/'
  7. URL_CLASH_UI="http://board.zash.run.place"
  8. SCRIPT_BASE_DIR='./script'
  9. SCRIPT_FISH="${SCRIPT_BASE_DIR}/clashctl.fish"
  10. RESOURCES_BASE_DIR='./resources'
  11. RESOURCES_BIN_DIR="${RESOURCES_BASE_DIR}/bin"
  12. RESOURCES_CONFIG="${RESOURCES_BASE_DIR}/config.yaml"
  13. RESOURCES_CONFIG_MIXIN="${RESOURCES_BASE_DIR}/mixin.yaml"
  14. ZIP_BASE_DIR="${RESOURCES_BASE_DIR}/zip"
  15. ZIP_CLASH=$(echo ${ZIP_BASE_DIR}/clash*)
  16. ZIP_MIHOMO=$(echo ${ZIP_BASE_DIR}/mihomo*)
  17. ZIP_YQ=$(echo ${ZIP_BASE_DIR}/yq*)
  18. ZIP_SUBCONVERTER=$(echo ${ZIP_BASE_DIR}/subconverter*)
  19. ZIP_UI="${ZIP_BASE_DIR}/yacd.tar.xz"
  20. CLASH_BASE_DIR='/develop/clash'
  21. CLASH_SCRIPT_DIR="${CLASH_BASE_DIR}/$(basename $SCRIPT_BASE_DIR)"
  22. CLASH_CONFIG_URL="${CLASH_BASE_DIR}/url"
  23. CLASH_CONFIG_RAW="${CLASH_BASE_DIR}/$(basename $RESOURCES_CONFIG)"
  24. CLASH_CONFIG_RAW_BAK="${CLASH_CONFIG_RAW}.bak"
  25. CLASH_CONFIG_MIXIN="${CLASH_BASE_DIR}/$(basename $RESOURCES_CONFIG_MIXIN)"
  26. CLASH_CONFIG_RUNTIME="${CLASH_BASE_DIR}/runtime.yaml"
  27. CLASH_UPDATE_LOG="${CLASH_BASE_DIR}/clashupdate.log"
  28. _set_var() {
  29. local user=$USER
  30. local home=$HOME
  31. [ -n "$SUDO_USER" ] && {
  32. user=$SUDO_USER
  33. home=$(awk -F: -v user="$SUDO_USER" '$1==user{print $6}' /etc/passwd)
  34. }
  35. [ -n "$BASH_VERSION" ] && {
  36. _SHELL=bash
  37. }
  38. [ -n "$ZSH_VERSION" ] && {
  39. _SHELL=zsh
  40. }
  41. [ -n "$fish_version" ] && {
  42. _SHELL=fish
  43. }
  44. # rc文件路径
  45. command -v bash >&/dev/null && {
  46. SHELL_RC_BASH="${home}/.bashrc"
  47. }
  48. command -v zsh >&/dev/null && {
  49. SHELL_RC_ZSH="${home}/.zshrc"
  50. }
  51. command -v fish >&/dev/null && {
  52. SHELL_RC_FISH="${home}/.config/fish/conf.d/clashctl.fish"
  53. }
  54. # 定时任务路径
  55. local os_info=$(cat /etc/os-release)
  56. echo "$os_info" | grep -iqsE "rhel|centos|openEuler|Rocky|AlmaLinux" && CLASH_CRON_TAB="/var/spool/cron/$user"
  57. echo "$os_info" | grep -iqsE "debian|ubuntu" && CLASH_CRON_TAB="/var/spool/cron/crontabs/$user"
  58. }
  59. _set_var
  60. # shellcheck disable=SC2120
  61. _set_bin() {
  62. local bin_base_dir="${CLASH_BASE_DIR}/bin"
  63. [ -n "$1" ] && bin_base_dir=$1
  64. BIN_CLASH="${bin_base_dir}/clash"
  65. BIN_MIHOMO="${bin_base_dir}/mihomo"
  66. BIN_YQ="${bin_base_dir}/yq"
  67. BIN_SUBCONVERTER_DIR="${bin_base_dir}/subconverter"
  68. BIN_SUBCONVERTER_CONFIG="$BIN_SUBCONVERTER_DIR/pref.yml"
  69. BIN_SUBCONVERTER_PORT="25500"
  70. BIN_SUBCONVERTER="${BIN_SUBCONVERTER_DIR}/subconverter"
  71. BIN_SUBCONVERTER_LOG="${BIN_SUBCONVERTER_DIR}/latest.log"
  72. [ -f "$BIN_CLASH" ] && {
  73. BIN_KERNEL=$BIN_CLASH
  74. }
  75. [ -f "$BIN_MIHOMO" ] && {
  76. BIN_KERNEL=$BIN_MIHOMO
  77. }
  78. BIN_KERNEL_NAME=$(basename "$BIN_KERNEL")
  79. }
  80. _set_bin
  81. _set_rc() {
  82. [ "$1" = "unset" ] && {
  83. sed -i "\|$CLASH_SCRIPT_DIR|d" "$SHELL_RC_BASH" "$SHELL_RC_ZSH" 2>/dev/null
  84. rm -f "$SHELL_RC_FISH" 2>/dev/null
  85. return
  86. }
  87. echo "source $CLASH_SCRIPT_DIR/common.sh && source $CLASH_SCRIPT_DIR/clashctl.sh" |
  88. tee -a "$SHELL_RC_BASH" "$SHELL_RC_ZSH" >&/dev/null
  89. [ -n "$SHELL_RC_FISH" ] && /usr/bin/install $SCRIPT_FISH "$SHELL_RC_FISH"
  90. }
  91. # 默认集成、安装mihomo内核
  92. # 移除/删除mihomo:下载安装clash内核
  93. function _get_kernel() {
  94. [ -f "$ZIP_CLASH" ] && {
  95. ZIP_KERNEL=$ZIP_CLASH
  96. BIN_KERNEL=$BIN_CLASH
  97. }
  98. [ -f "$ZIP_MIHOMO" ] && {
  99. ZIP_KERNEL=$ZIP_MIHOMO
  100. BIN_KERNEL=$BIN_MIHOMO
  101. }
  102. [ ! -f "$ZIP_MIHOMO" ] && [ ! -f "$ZIP_CLASH" ] && {
  103. local arch=$(uname -m)
  104. _failcat "${ZIP_BASE_DIR}:未检测到可用的内核压缩包"
  105. _download_clash "$arch"
  106. ZIP_KERNEL=$ZIP_CLASH
  107. BIN_KERNEL=$BIN_CLASH
  108. }
  109. BIN_KERNEL_NAME=$(basename "$BIN_KERNEL")
  110. _okcat "安装内核:$BIN_KERNEL_NAME"
  111. }
  112. _get_random_port() {
  113. local randomPort=$(shuf -i 1024-65535 -n 1)
  114. ! _is_bind "$randomPort" && { echo "$randomPort" && return; }
  115. _get_random_port
  116. }
  117. function _get_proxy_port() {
  118. MIXED_PORT=$(sudo "$BIN_YQ" '.mixed-port' $CLASH_CONFIG_RUNTIME)
  119. _is_already_in_use "$MIXED_PORT" "$BIN_KERNEL_NAME" && {
  120. local newPort=$(_get_random_port)
  121. local msg="端口占用:${MIXED_PORT} 🎲 随机分配:$newPort"
  122. sudo "$BIN_YQ" -i ".mixed-port = $newPort" $CLASH_CONFIG_RUNTIME
  123. MIXED_PORT=$newPort
  124. _failcat '🎯' "$msg"
  125. }
  126. }
  127. function _get_ui_port() {
  128. local ext_addr=$(sudo "$BIN_YQ" '.external-controller // ""' $CLASH_CONFIG_RUNTIME)
  129. local ext_ip=${ext_addr%%:*}
  130. EXT_IP=$ext_ip
  131. EXT_PORT=${ext_addr##*:}
  132. # ip route get 1.1.1.1 | grep -oP 'src \K\S+'
  133. [ "$ext_ip" = '0.0.0.0' ] && EXT_IP=$(hostname -I | awk '{print $1}')
  134. _is_already_in_use "$EXT_PORT" "$BIN_KERNEL_NAME" && {
  135. local newPort=$(_get_random_port)
  136. local msg="端口占用:${EXT_PORT} 🎲 随机分配:$newPort"
  137. sudo "$BIN_YQ" -i ".external-controller = \"$ext_ip:$newPort\"" $CLASH_CONFIG_RUNTIME
  138. EXT_PORT=$newPort
  139. _failcat '🎯' "$msg"
  140. }
  141. }
  142. _get_color() {
  143. local hex="${1#\#}"
  144. local r=$((16#${hex:0:2}))
  145. local g=$((16#${hex:2:2}))
  146. local b=$((16#${hex:4:2}))
  147. printf "\e[38;2;%d;%d;%dm" "$r" "$g" "$b"
  148. }
  149. _get_color_msg() {
  150. local color=$(_get_color "$1")
  151. local msg=$2
  152. local reset="\033[0m"
  153. printf "%b%s%b\n" "$color" "$msg" "$reset"
  154. }
  155. _get_random_val() {
  156. cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 6
  157. }
  158. function _okcat() {
  159. local color=#c8d6e5
  160. local emoji=😼
  161. [ $# -gt 1 ] && emoji=$1 && shift
  162. local msg="${emoji} $1"
  163. _get_color_msg "$color" "$msg" && return 0
  164. }
  165. function _failcat() {
  166. local color=#fd79a8
  167. local emoji=😾
  168. [ $# -gt 1 ] && emoji=$1 && shift
  169. local msg="${emoji} $1"
  170. _get_color_msg "$color" "$msg" >&2 && return 1
  171. }
  172. function _quit() {
  173. local user=root
  174. [ -n "$SUDO_USER" ] && user=$SUDO_USER
  175. exec sudo -u "$user" -- "$_SHELL" -i
  176. }
  177. function _error_quit() {
  178. [ $# -gt 0 ] && {
  179. local color=#f92f60
  180. local emoji=📢
  181. [ $# -gt 1 ] && emoji=$1 && shift
  182. local msg="${emoji} $1"
  183. _get_color_msg "$color" "$msg"
  184. }
  185. exec $_SHELL -i
  186. }
  187. _is_bind() {
  188. local port=$1
  189. { sudo ss -lnptu || sudo netstat -lnptu; } | grep ":${port}\b"
  190. }
  191. _is_already_in_use() {
  192. local port=$1
  193. local progress=$2
  194. _is_bind "$port" | grep -qs -v "$progress"
  195. }
  196. function _is_root() {
  197. [ "$(whoami)" = "root" ]
  198. }
  199. function _valid_env() {
  200. _is_root || _error_quit "需要 root 或 sudo 权限执行"
  201. [ "$(ps -p 1 -o comm=)" != "systemd" ] && _error_quit "系统不具备 systemd"
  202. }
  203. function _valid_config() {
  204. [ -e "$1" ] && [ "$(wc -l <"$1")" -gt 1 ] && {
  205. local cmd msg
  206. cmd="sudo $BIN_KERNEL -d $(dirname "$1") -f $1 -t"
  207. local is_dat=$(sudo "$BIN_YQ" '.geodata-mode // false' "$1")
  208. [ "$is_dat" = "true" ] && {
  209. sudo "$BIN_YQ" -i ".geodata-mode = false" "$1"
  210. }
  211. msg=$(eval "$cmd") || {
  212. eval "$cmd"
  213. echo "$msg" | grep -qs "unsupport proxy type" && {
  214. local prefix="检测到订阅中包含不受支持的代理协议"
  215. [ "$BIN_KERNEL_NAME" = "clash" ] && _error_quit "${prefix}, 推荐安装使用 mihomo 内核"
  216. _error_quit "${prefix}, 请检查并升级内核版本"
  217. }
  218. }
  219. }
  220. }
  221. _download_clash() {
  222. local arch=$1
  223. local url sha256sum
  224. case "$arch" in
  225. x86_64)
  226. url=https://downloads.clash.wiki/ClashPremium/clash-linux-amd64-2023.08.17.gz
  227. sha256sum='92380f053f083e3794c1681583be013a57b160292d1d9e1056e7fa1c2d948747'
  228. ;;
  229. *86*)
  230. url=https://downloads.clash.wiki/ClashPremium/clash-linux-386-2023.08.17.gz
  231. sha256sum='254125efa731ade3c1bf7cfd83ae09a824e1361592ccd7c0cccd2a266dcb92b5'
  232. ;;
  233. armv*)
  234. url=https://downloads.clash.wiki/ClashPremium/clash-linux-armv5-2023.08.17.gz
  235. sha256sum='622f5e774847782b6d54066f0716114a088f143f9bdd37edf3394ae8253062e8'
  236. ;;
  237. aarch64)
  238. url=https://downloads.clash.wiki/ClashPremium/clash-linux-arm64-2023.08.17.gz
  239. sha256sum='c45b39bb241e270ae5f4498e2af75cecc0f03c9db3c0db5e55c8c4919f01afdd'
  240. ;;
  241. *)
  242. _error_quit "未知的架构版本:$arch,请自行下载对应版本至 ${ZIP_BASE_DIR} 目录下:https://downloads.clash.wiki/ClashPremium/"
  243. ;;
  244. esac
  245. _okcat '⏳' "正在下载:clash:${arch} 架构..."
  246. ZIP_CLASH="${ZIP_BASE_DIR}/$(basename $url)"
  247. curl \
  248. --progress-bar \
  249. --show-error \
  250. --fail \
  251. --insecure \
  252. --location \
  253. --connect-timeout 5 \
  254. --max-time 15 \
  255. --retry 1 \
  256. --output "$ZIP_CLASH" \
  257. "$url"
  258. echo $sha256sum "$ZIP_CLASH" | sha256sum -c ||
  259. _error_quit "下载失败:请自行下载对应版本至 ${ZIP_BASE_DIR} 目录下:https://downloads.clash.wiki/ClashPremium/"
  260. }
  261. _download_raw_config() {
  262. local dest=$1
  263. local url=$2
  264. local agent='clash-verge/v2.0.4'
  265. sudo curl \
  266. --silent \
  267. --show-error \
  268. --insecure \
  269. --location \
  270. --max-time 5 \
  271. --retry 1 \
  272. --user-agent "$agent" \
  273. --output "$dest" \
  274. "$url" ||
  275. sudo wget \
  276. --no-verbose \
  277. --no-check-certificate \
  278. --timeout 3 \
  279. --tries 1 \
  280. --user-agent "$agent" \
  281. --output-document "$dest" \
  282. "$url"
  283. }
  284. _download_convert_config() {
  285. local dest=$1
  286. local url=$2
  287. _start_convert
  288. local convert_url=$(
  289. target='clash'
  290. base_url="http://127.0.0.1:${BIN_SUBCONVERTER_PORT}/sub"
  291. curl \
  292. --get \
  293. --silent \
  294. --location \
  295. --output /dev/null \
  296. --data-urlencode "target=$target" \
  297. --data-urlencode "url=$url" \
  298. --write-out '%{url_effective}' \
  299. "$base_url"
  300. )
  301. _download_raw_config "$dest" "$convert_url"
  302. _stop_convert
  303. }
  304. function _download_config() {
  305. local dest=$1
  306. local url=$2
  307. [ "${url:0:4}" = 'file' ] && return 0
  308. _download_raw_config "$dest" "$url" || return 1
  309. _okcat '🍃' '下载成功:内核验证配置...'
  310. _valid_config "$dest" || {
  311. _failcat '🍂' "验证失败:尝试订阅转换..."
  312. _download_convert_config "$dest" "$url" || _failcat '🍂' "转换失败:请检查日志:$BIN_SUBCONVERTER_LOG"
  313. }
  314. }
  315. _start_convert() {
  316. _is_already_in_use $BIN_SUBCONVERTER_PORT 'subconverter' && {
  317. local newPort=$(_get_random_port)
  318. _failcat '🎯' "端口占用:$BIN_SUBCONVERTER_PORT 🎲 随机分配:$newPort"
  319. [ ! -e "$BIN_SUBCONVERTER_CONFIG" ] && {
  320. sudo /bin/cp -f "$BIN_SUBCONVERTER_DIR/pref.example.yml" "$BIN_SUBCONVERTER_CONFIG"
  321. }
  322. sudo "$BIN_YQ" -i ".server.port = $newPort" "$BIN_SUBCONVERTER_CONFIG"
  323. BIN_SUBCONVERTER_PORT=$newPort
  324. }
  325. local start=$(date +%s)
  326. # 子shell运行,屏蔽kill时的输出
  327. (sudo "$BIN_SUBCONVERTER" 2>&1 | sudo tee "$BIN_SUBCONVERTER_LOG" >/dev/null &)
  328. while ! _is_bind "$BIN_SUBCONVERTER_PORT" >&/dev/null; do
  329. sleep 1s
  330. local now=$(date +%s)
  331. [ $((now - start)) -gt 1 ] && _error_quit "订阅转换服务未启动,请检查日志:$BIN_SUBCONVERTER_LOG"
  332. done
  333. }
  334. _stop_convert() {
  335. sudo pkill -9 -f "$BIN_SUBCONVERTER" >&/dev/null
  336. }