diff --git a/install.sh b/install.sh index 968f87e..5de0bed 100644 --- a/install.sh +++ b/install.sh @@ -3309,6 +3309,7 @@ addSingBoxRouteRule() { domainList="${domainList},$(jq -rc .route.rules[0].rule_set[] "${singBoxConfigPath}${routingName}.json" | awk -F "[_]" '{print $1}' | paste -sd ',')" domainList="${domainList},$(jq -rc .route.rules[0].domain_regex[] "${singBoxConfigPath}${routingName}.json" | awk -F "[*]" '{print $2}' | paste -sd ',' | sed 's/\\//g')" fi + fi local rules= rules=$(initSingBoxRules "${domainList}" "${routingName}") @@ -3326,7 +3327,6 @@ addSingBoxRouteRule() { ruleSetTag=$(echo "${ruleSet}" | jq '.|map(.tag)') fi if [[ -n "${singBoxConfigPath}" ]]; then - cat <"${singBoxConfigPath}${routingName}.json" { "route": { @@ -6637,8 +6637,10 @@ blacklist() { echoContent red "\n==============================================================" echoContent yellow "1.查看已屏蔽域名" echoContent yellow "2.添加域名" - echoContent yellow "3.屏蔽大陆域名" - echoContent yellow "4.卸载黑名单" + echoContent yellow "3.屏蔽大陆域名+IP" + echoContent yellow "4.卸载黑/白名单" + echoContent yellow "5.添加IP" + echoContent yellow "6.添加域名白名单" echoContent red "==============================================================" read -r -p "请选择:" blacklistStatus @@ -6667,54 +6669,231 @@ blacklist() { echoContent green " ---> 添加完毕" elif [[ "${blacklistStatus}" == "3" ]]; then + local allowDomainList="dl.google.com,apple.com,bing.com,microsoft.com,gstatic,xn--ngstr-lra8j.com,googleapis.com,googleapis.cn" if [[ "${coreInstallType}" == "1" ]]; then unInstallRouting blackhole_out outboundTag + unInstallRouting blackhole_ip_out outboundTag addXrayRouting blackhole_out outboundTag "cn" + addXrayIPRouting blackhole_ip_out outboundTag "cn" + addXrayRouting allow_domain_direct_outbound outboundTag "${allowDomainList}" "top" addXrayOutbound blackhole_out + addXrayOutbound blackhole_ip_out + addXrayOutbound allow_domain_direct_outbound fi if [[ -n "${singBoxConfigPath}" ]]; then addSingBoxRouteRule "cn_block_outbound" "cn" "cn_block_route" - - addSingBoxRouteRule "01_direct_outbound" "googleapis.com,googleapis.cn,xn--ngstr-lra8j.com,gstatic.com" "cn_01_google_play_route" + addSingBoxGeoIPRouteRule "block_ip_outbound" "cn" "cn_block_ip_route" + addSingBoxRouteRule "01_direct_outbound" "${allowDomainList}" "00_allow_domain_route" addSingBoxOutbound "cn_block_outbound" + addSingBoxOutbound "block_ip_outbound" addSingBoxOutbound "01_direct_outbound" fi - echoContent green " ---> 屏蔽大陆域名完毕" + echoContent green " ---> 屏蔽大陆域名+IP完毕" elif [[ "${blacklistStatus}" == "4" ]]; then if [[ "${coreInstallType}" == "1" ]]; then unInstallRouting blackhole_out outboundTag + unInstallRouting blackhole_ip_out outboundTag + unInstallRouting allow_domain_direct_outbound outboundTag + + removeXrayOutbound blackhole_ip_out + removeXrayOutbound allow_domain_direct_outbound fi if [[ -n "${singBoxConfigPath}" ]]; then removeSingBoxConfig "cn_block_route" removeSingBoxConfig "cn_block_outbound" + removeSingBoxConfig "cn_block_ip_route" + removeSingBoxConfig "block_ip_route" + removeSingBoxConfig "block_ip_outbound" removeSingBoxConfig "cn_01_google_play_route" + removeSingBoxConfig "00_allow_domain_route" removeSingBoxConfig "block_domain_route" removeSingBoxConfig "block_domain_outbound" fi - echoContent green " ---> 域名黑名单删除完毕" + echoContent green " ---> 域名黑名单/白名单删除完毕" + elif [[ "${blacklistStatus}" == "5" ]]; then + echoContent red "==============================================================" + echoContent yellow "录入示例:1.1.1.1,8.8.8.8,1.1.1.0/24,2400:3200::/32\n" + read -r -p "请按照上面示例录入IP:" ipList + if [[ -z "${ipList}" ]]; then + echoContent red " ---> IP不可为空" + exit 0 + fi + + if [[ "${coreInstallType}" == "1" ]]; then + addXrayIPRouting blackhole_ip_out outboundTag "${ipList}" + addXrayOutbound blackhole_ip_out + fi + + if [[ -n "${singBoxConfigPath}" ]]; then + addSingBoxIPRouteRule "block_ip_outbound" "${ipList}" "block_ip_route" + addSingBoxOutbound "block_ip_outbound" + fi + echoContent green " ---> 添加IP完毕" + elif [[ "${blacklistStatus}" == "6" ]]; then + echoContent red "==============================================================" + echoContent yellow "录入示例:speedtest,openai,google.com\n" + read -r -p "请按照上面示例录入域名:" allowDomainList + if [[ -z "${allowDomainList}" ]]; then + echoContent red " ---> 域名不可为空" + exit 0 + fi + + if [[ "${coreInstallType}" == "1" ]]; then + addXrayRouting allow_domain_direct_outbound outboundTag "${allowDomainList}" "top" + addXrayOutbound allow_domain_direct_outbound + fi + + if [[ -n "${singBoxConfigPath}" ]]; then + addSingBoxRouteRule "01_direct_outbound" "${allowDomainList}" "00_allow_domain_route" + addSingBoxOutbound "01_direct_outbound" + fi + echoContent green " ---> 添加域名白名单完毕" else echoContent red " ---> 选择错误" exit 0 fi reloadCore } +# 下载 dlc.dat_plain.yml 到核心目录 +downloadDLCPlainYAML() { + local corePath=$1 + local dlcFilePath="${corePath}/dlc.dat_plain.yml" + local tmpFilePath="${dlcFilePath}.tmp" + + if [[ -z "${corePath}" ]]; then + return 1 + fi + + mkdir -p "${corePath}" >/dev/null 2>&1 + if [[ -s "${dlcFilePath}" ]]; then + return 0 + fi + local dlcDownloadURL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml" + if [[ "${release}" == "alpine" ]]; then + wget -c -O "${tmpFilePath}" "${dlcDownloadURL}" >/dev/null 2>&1 + else + wget -c "${wgetShowProgressStatus}" -O "${tmpFilePath}" "${dlcDownloadURL}" >/dev/null 2>&1 + fi + + # shellcheck disable=SC2181 + if [[ "$?" -ne 0 || ! -s "${tmpFilePath}" ]]; then + rm -f "${tmpFilePath}" >/dev/null 2>&1 + return 1 + fi + + mv "${tmpFilePath}" "${dlcFilePath}" >/dev/null 2>&1 +} + +# 转义grep/regex匹配字符 +escapeDLCRegexPattern() { + # shellcheck disable=SC2016 + # shellcheck disable=SC2001 + echo "$1" | sed -e 's/[.[\*^$()+?{|]/\\&/g' +} + +# 根据规则行号向上回溯对应name +getDLCNameByRuleLine() { + local ruleLine=$1 + local dlcFilePath=$2 + awk -v targetLine="${ruleLine}" ' + /^[[:space:]]*-[[:space:]]*name:[[:space:]]*/ { + line = $0 + sub(/^[[:space:]]*-[[:space:]]*name:[[:space:]]*/, "", line) + currentName = line + } + NR == targetLine { + print currentName + exit + }' "${dlcFilePath}" +} + +isDomainFormat() { + local target=$1 + [[ "${target}" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z0-9-]{2,63}$ ]] +} + +# 根据输入域名匹配 dlc.dat_plain.yml 对应 geosite name +getDLCGeositeName() { + local inputRule=$1 + local corePath=$2 + local dlcFilePath="${corePath}/dlc.dat_plain.yml" + + if [[ -z "${inputRule}" || -z "${corePath}" ]]; then + echo "" + return + fi + + local normalizedInput + normalizedInput=$(echo "${inputRule}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + normalizedInput=${normalizedInput#domain:} + normalizedInput=${normalizedInput#full:} + normalizedInput=${normalizedInput#keyword:} + + if [[ -z "${normalizedInput}" ]]; then + echo "" + return + fi + + if isDomainFormat "${normalizedInput}"; then + return + fi + + if ! downloadDLCPlainYAML "${corePath}"; then + echo "" + return + fi + + local escapedInput= + escapedInput=$(escapeDLCRegexPattern "${normalizedInput}") + + local matchedLine= + matchedLine=$(grep -n -m1 -E "^[[:space:]]*-[[:space:]]*name:[[:space:]]*${escapedInput}[[:space:]]*$" "${dlcFilePath}") + if [[ -n "${matchedLine}" ]]; then + echo "${normalizedInput}" + fi +} + +# 获取规则匹配结果,优先geosite,失败回退domain +getDLCMatchedRuleValue() { + local inputRule=$1 + local corePath=$2 + local normalizedInput= + normalizedInput=$(echo "${inputRule}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + if isDomainFormat "${normalizedInput}"; then + local escapedDomain= + escapedDomain=$(escapeDLCRegexPattern "${normalizedInput}") + echo "regexp:.*${escapedDomain}.*" + return + fi + + local matchedRuleName= + matchedRuleName=$(getDLCGeositeName "${normalizedInput}" "${corePath}") + if [[ -n "${matchedRuleName}" ]]; then + echo "geosite:${matchedRuleName}" + else + echo "domain:${normalizedInput}" + fi +} + # 添加routing配置 addXrayRouting() { local tag=$1 # warp-socks local type=$2 # outboundTag/inboundTag local domain=$3 # 域名 + local rulePosition=$4 if [[ -z "${tag}" || -z "${type}" || -z "${domain}" ]]; then echoContent red " ---> 参数错误" @@ -6750,27 +6929,146 @@ EOF if echo "${routingRule}" | grep -q "${line}"; then echoContent yellow " ---> ${line}已存在,跳过" else - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/v2fly/domain-list-community/contents/data/${line}" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - routingRule=$(echo "${routingRule}" | jq -r '.domain += ["geosite:'"${line}"'"]') - else - routingRule=$(echo "${routingRule}" | jq -r '.domain += ["domain:'"${line}"'"]') - fi + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + routingRule=$(echo "${routingRule}" | jq -r --arg rule "${matchedRuleValue}" '.domain += [$rule]') fi done < <(echo "${domain}" | tr ',' '\n') unInstallRouting "${tag}" "${type}" if ! grep -q "gstatic.com" ${configPath}09_routing.json && [[ "${tag}" == "blackhole_out" ]]; then local routing= - routing=$(jq -r ".routing.rules += [{\"type\": \"field\",\"domain\": [\"gstatic.com\"],\"outboundTag\": \"direct\"}]" ${configPath}09_routing.json) + routing=$(jq -r ".routing.rules += [{\"type\": \"field\",\"domain\": [\"domain:gstatic.com\"],\"outboundTag\": \"allow_domain_direct_outbound\"}]" ${configPath}09_routing.json) echo "${routing}" | jq . >${configPath}09_routing.json + addXrayOutbound allow_domain_direct_outbound fi + if [[ "${rulePosition}" == "top" ]]; then + routing=$(jq -r ".routing.rules = [${routingRule}] + .routing.rules" ${configPath}09_routing.json) + else + routing=$(jq -r ".routing.rules += [${routingRule}]" ${configPath}09_routing.json) + fi + echo "${routing}" | jq . >${configPath}09_routing.json +} + +# 添加 Xray IP 屏蔽路由规则 +# 支持 geoip:cn 与自定义 IPv4/IPv6/CIDR +addXrayIPRouting() { + + local tag=$1 + local type=$2 + local ipList=$3 + + if [[ -z "${tag}" || -z "${type}" || -z "${ipList}" ]]; then + echoContent red " ---> 参数错误" + exit 0 + fi + + if [[ ! -f "${configPath}09_routing.json" ]]; then + cat <${configPath}09_routing.json +{ + "routing":{ + "type": "field", + "rules": [] + } +} +EOF + fi + + local routingRule= + routingRule=$(jq -r ".routing.rules[]|select(.outboundTag==\"${tag}\" and (.protocol == null) and (.ip != null))" ${configPath}09_routing.json) + if [[ -z "${routingRule}" ]]; then + routingRule="{\"type\": \"field\",\"ip\": [],\"outboundTag\": \"${tag}\"}" + fi + + while read -r line; do + line=$(echo "${line}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + if [[ -z "${line}" ]]; then + continue + fi + + local ipRuleValue=${line} + if [[ "${line}" == "cn" ]]; then + ipRuleValue="geoip:cn" + fi + + if echo "${routingRule}" | grep -q "${ipRuleValue}"; then + echoContent yellow " ---> ${ipRuleValue}已存在,跳过" + else + routingRule=$(echo "${routingRule}" | jq -r '.ip += ["'"${ipRuleValue}"'"]') + fi + done < <(echo "${ipList}" | tr ',' '\n') + + unInstallRouting "${tag}" "${type}" + local routing= routing=$(jq -r ".routing.rules += [${routingRule}]" ${configPath}09_routing.json) echo "${routing}" | jq . >${configPath}09_routing.json } + +# 添加 sing-box IP 屏蔽路由规则 +# 支持增量合并历史 ip_cidr +addSingBoxIPRouteRule() { + local outboundTag=$1 + local ipList=$2 + local routingName=$3 + + local historyIPs= + if [[ -f "${singBoxConfigPath}${routingName}.json" ]]; then + historyIPs=$(jq -r '.route.rules[0].ip_cidr[]?' "${singBoxConfigPath}${routingName}.json" | paste -sd ',') + fi + + if [[ -n "${historyIPs}" ]]; then + ipList="${ipList},${historyIPs}" + fi + + local ipCIDR=[] + ipCIDR=$(echo "${ipList}" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$' | sort -n | uniq | jq -R . | jq -s .) + + cat <"${singBoxConfigPath}${routingName}.json" +{ + "route": { + "rules": [ + { + "ip_cidr": ${ipCIDR}, + "outbound": "${outboundTag}" + } + ] + } +} +EOF +} + +# 添加 sing-box GeoIP 远程规则 +# 用于大陆 IP 自动屏蔽场景 +addSingBoxGeoIPRouteRule() { + local outboundTag=$1 + local geoipCode=$2 + local routingName=$3 + + cat <"${singBoxConfigPath}${routingName}.json" +{ + "route": { + "rules": [ + { + "rule_set": [ + "geoip_${geoipCode}_${routingName}" + ], + "outbound": "${outboundTag}" + } + ], + "rule_set": [ + { + "tag": "geoip_${geoipCode}_${routingName}", + "type": "remote", + "format": "binary", + "url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-${geoipCode}.srs", + "download_detour": "01_direct_outbound" + } + ] + } +} +EOF +} # 根据tag卸载Routing unInstallRouting() { local tag=$1 @@ -7446,13 +7744,21 @@ initSingBoxRules() { local domainRules=[] local ruleSet=[] while read -r line; do - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/SagerNet/sing-geosite/contents/geosite-${line}.srs?ref=rule-set" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - ruleSet=$(echo "${ruleSet}" | jq -r ". += [{\"tag\":\"${line}_$2\",\"type\":\"remote\",\"format\":\"binary\",\"url\":\"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-${line}.srs\",\"download_detour\":\"01_direct_outbound\"}]") + local normalizedLine= + normalizedLine=$(echo "${line}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + if isDomainFormat "${normalizedLine}"; then + local escapedDomain= + escapedDomain=${normalizedLine//./\\.} + domainRules=$(echo "${domainRules}" | jq -r --arg reg ".*${escapedDomain}.*" '. += [$reg]') else - domainRules=$(echo "${domainRules}" | jq -r ". += [\"^([a-zA-Z0-9_-]+\\\.)*${line//./\\\\.}\"]") + local matchedRuleName + matchedRuleName=$(getDLCGeositeName "${normalizedLine}" "/etc/v2ray-agent/sing-box") + + if [[ -n "${matchedRuleName}" ]]; then + ruleSet=$(echo "${ruleSet}" | jq -r ". += [{\"tag\":\"${matchedRuleName}_$2\",\"type\":\"remote\",\"format\":\"binary\",\"url\":\"https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-${matchedRuleName}.srs\",\"download_detour\":\"01_direct_outbound\"}]") + else + domainRules=$(echo "${domainRules}" | jq -r --arg reg "^([a-zA-Z0-9_-]+\\.)*${normalizedLine//./\\.}" '. += [$reg]') + fi fi done < <(echo "$1" | tr ',' '\n' | grep -v '^$' | sort -n | uniq | paste -sd ',' | tr ',' '\n') echo "{ \"domainRules\":${domainRules},\"ruleSet\":${ruleSet}}" @@ -7635,14 +7941,9 @@ setSocks5OutboundRouting() { if echo "${routingRule}" | grep -q "${line}"; then echoContent yellow " ---> ${line}已存在,跳过" else - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/v2fly/domain-list-community/contents/data/${line}" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - domainRules=$(echo "${domainRules}" | jq -r ". += [\"geosite:${line}\"]") - else - domainRules=$(echo "${domainRules}" | jq -r ". += [\"domain:${line}\"]") - fi + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + domainRules=$(echo "${domainRules}" | jq -r --arg rule "${matchedRuleValue}" '. += [$rule]') fi done < <(echo "${socks5RoutingOutboundDomain}" | tr ',' '\n') if [[ ! -f "${configPath}09_routing.json" ]]; then @@ -7793,7 +8094,9 @@ setUnlockSNI() { read -r -p "请按照上面示例录入域名:" xrayDomainList local hosts={} while read -r domain; do - hosts=$(echo "${hosts}" | jq -r ".\"geosite:${domain}\"=\"${setSNIP}\"") + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${domain}" "/etc/v2ray-agent/xray") + hosts=$(echo "${hosts}" | jq -r --arg key "${matchedRuleValue}" --arg value "${setSNIP}" '. + {($key):$value}') done < <(echo "${xrayDomainList}" | tr ',' '\n') cat <${configPath}11_dns.json { @@ -7826,14 +8129,9 @@ addXrayDNSConfig() { local domainList=$2 local domains=[] while read -r line; do - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/v2fly/domain-list-community/contents/data/${line}" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - domains=$(echo "${domains}" | jq -r '. += ["geosite:'"${line}"'"]') - else - domains=$(echo "${domains}" | jq -r '. += ["domain:'"${line}"'"]') - fi + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + domains=$(echo "${domains}" | jq -r --arg rule "${matchedRuleValue}" '. += [$rule]') done < <(echo "${domainList}" | tr ',' '\n') if [[ "${coreInstallType}" == "1" ]]; then @@ -9649,7 +9947,7 @@ menu() { cd "$HOME" || exit echoContent red "\n==============================================================" echoContent green "作者:mack-a" - echoContent green "当前版本:v3.5.12" + echoContent green "当前版本:v3.5.13" echoContent green "Github:https://github.com/mack-a/v2ray-agent" echoContent green "描述:八合一共存脚本\c" showInstallStatus diff --git a/shell/install_en.sh b/shell/install_en.sh index edf181a..4c82d02 100644 --- a/shell/install_en.sh +++ b/shell/install_en.sh @@ -5545,8 +5545,10 @@ blacklist() { echoContent red "\n================================================ =================" echoContent yellow "1.View blocked domain names" echoContent yellow "2.Add domain name" - echoContent yellow "3.Block domestic domain names" - echoContent yellow "4.Delete blacklist" + echoContent yellow "3.Block domestic domain names + IP" + echoContent yellow "4.Delete blacklist/whitelist" + echoContent yellow "5.Add blocked IP" + echoContent yellow "6.Add domain whitelist" echoContent red "================================================== ===============" read -r -p "Please select:" blacklistStatus @@ -5575,33 +5577,194 @@ blacklist() { echoContent green " ---> Added successfully" elif [[ "${blacklistStatus}" == "3" ]]; then + local autoAllowDomainList="dl.google.com,apple.com,bing.com,microsoft.com,gstatic.cn" + unInstallRouting blackhole-out outboundTag + unInstallRouting blackhole-ip-out outboundTag + addInstallRouting blackhole-out outboundTag "cn" + addInstallIPRouting blackhole-ip-out outboundTag "cn" + addInstallRouting allow-domain-direct-out outboundTag "${autoAllowDomainList}" "top" unInstallOutbounds blackhole-out + unInstallOutbounds blackhole-ip-out + unInstallOutbounds allow-domain-direct-out - outbounds=$(jq -r '.outbounds += [{"protocol":"blackhole","tag":"blackhole-out"}]' ${configPath}10_ipv4_outbounds.json) + outbounds=$(jq -r '.outbounds += [{"protocol":"blackhole","tag":"blackhole-out"},{"protocol":"blackhole","tag":"blackhole-ip-out"},{"protocol":"freedom","tag":"allow-domain-direct-out","settings":{"domainStrategy":"UseIP"}}]' ${configPath}10_ipv4_outbounds.json) echo "${outbounds}" | jq . >${configPath}10_ipv4_outbounds.json - echoContent green " ---> Domestic domain name blocked successfully" + echoContent green " ---> Domestic domain name + IP blocked successfully" elif [[ "${blacklistStatus}" == "4" ]]; then unInstallRouting blackhole-out outboundTag + unInstallRouting blackhole-ip-out outboundTag + unInstallRouting allow-domain-direct-out outboundTag - echoContent green " ---> Domain name blacklist deleted successfully" + unInstallOutbounds blackhole-ip-out + unInstallOutbounds allow-domain-direct-out + + echoContent green " ---> Domain blacklist/whitelist deleted successfully" + elif [[ "${blacklistStatus}" == "5" ]]; then + echoContent yellow "Input example:1.1.1.1,8.8.8.8,1.1.1.0/24,2400:3200::/32\n" + read -r -p "Please enter the blocked IP list:" ipList + if [[ -z "${ipList}" ]]; then + echoContent red " ---> ip cannot be empty" + exit 0 + fi + + addInstallIPRouting blackhole-ip-out outboundTag "${ipList}" + unInstallOutbounds blackhole-ip-out + outbounds=$(jq -r '.outbounds += [{"protocol":"blackhole","tag":"blackhole-ip-out"}]' ${configPath}10_ipv4_outbounds.json) + echo "${outbounds}" | jq . >${configPath}10_ipv4_outbounds.json + echoContent green " ---> Blocked IP added successfully" + elif [[ "${blacklistStatus}" == "6" ]]; then + echoContent yellow "Input example:speedtest,openai,google.com\n" + read -r -p "Please enter whitelist domains:" allowDomainList + if [[ -z "${allowDomainList}" ]]; then + echoContent red " ---> domain cannot be empty" + exit 0 + fi + + addInstallRouting allow-domain-direct-out outboundTag "${allowDomainList}" "top" + unInstallOutbounds allow-domain-direct-out + outbounds=$(jq -r '.outbounds += [{"protocol":"freedom","tag":"allow-domain-direct-out","settings":{"domainStrategy":"UseIP"}}]' ${configPath}10_ipv4_outbounds.json) + echo "${outbounds}" | jq . >${configPath}10_ipv4_outbounds.json + echoContent green " ---> Domain whitelist added successfully" else echoContent red " ---> Wrong selection" exit 0 fi reloadCore } + +# Download dlc.dat_plain.yml into core directory +downloadDLCPlainYAML() { + local corePath=$1 + local dlcFilePath="${corePath}/dlc.dat_plain.yml" + local tmpFilePath="${dlcFilePath}.tmp" + + if [[ -z "${corePath}" ]]; then + return 1 + fi + + mkdir -p "${corePath}" >/dev/null 2>&1 + if [[ -s "${dlcFilePath}" ]]; then + return 0 + fi + + local dlcDownloadURL="https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml" + if [[ "${release}" == "alpine" ]]; then + wget -c -q -O "${tmpFilePath}" "${dlcDownloadURL}" >/dev/null 2>&1 + else + wget -c -q "${wgetShowProgressStatus}" -O "${tmpFilePath}" "${dlcDownloadURL}" >/dev/null 2>&1 + fi + + if [[ $? -ne 0 || ! -s "${tmpFilePath}" ]]; then + rm -f "${tmpFilePath}" >/dev/null 2>&1 + return 1 + fi + + mv "${tmpFilePath}" "${dlcFilePath}" >/dev/null 2>&1 +} + +# Escape grep/regex pattern meta characters +escapeDLCRegexPattern() { + echo "$1" | sed -e 's/[.[\\*^$()+?{|]/\\\\&/g' +} + +# Resolve nearest upper name by matched rule line +getDLCNameByRuleLine() { + local ruleLine=$1 + local dlcFilePath=$2 + awk -v targetLine="${ruleLine}" ' + /^[[:space:]]*-[[:space:]]*name:[[:space:]]*/ { + line = $0 + sub(/^[[:space:]]*-[[:space:]]*name:[[:space:]]*/, "", line) + currentName = line + } + NR == targetLine { + print currentName + exit + }' "${dlcFilePath}" +} + +isDomainFormat() { + local target=$1 + [[ "${target}" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z0-9-]{2,63}$ ]] +} + +# Resolve geosite name from dlc.dat_plain.yml by input domain +getDLCGeositeName() { + local inputRule=$1 + local corePath=$2 + local dlcFilePath="${corePath}/dlc.dat_plain.yml" + + if [[ -z "${inputRule}" || -z "${corePath}" ]]; then + echo "" + return + fi + + local normalizedInput + normalizedInput=$(echo "${inputRule}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + normalizedInput=${normalizedInput#domain:} + normalizedInput=${normalizedInput#full:} + normalizedInput=${normalizedInput#keyword:} + + if [[ -z "${normalizedInput}" ]]; then + echo "" + return + fi + + if isDomainFormat "${normalizedInput}"; then + return + fi + + if ! downloadDLCPlainYAML "${corePath}"; then + echo "" + return + fi + + local escapedInput= + escapedInput=$(escapeDLCRegexPattern "${normalizedInput}") + + local matchedLine= + matchedLine=$(grep -n -m1 -E "^[[:space:]]*-[[:space:]]*name:[[:space:]]*${escapedInput}[[:space:]]*$" "${dlcFilePath}") + if [[ -n "${matchedLine}" ]]; then + echo "${normalizedInput}" + fi +} + +# Build final rule value: prefer geosite, fallback to domain +getDLCMatchedRuleValue() { + local inputRule=$1 + local corePath=$2 + local normalizedInput= + normalizedInput=$(echo "${inputRule}" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + if isDomainFormat "${normalizedInput}"; then + local escapedDomain= + escapedDomain=$(escapeDLCRegexPattern "${normalizedInput}") + echo "regexp:.*${escapedDomain}.*" + return + fi + + local matchedRuleName= + matchedRuleName=$(getDLCGeositeName "${normalizedInput}" "${corePath}") + if [[ -n "${matchedRuleName}" ]]; then + echo "geosite:${matchedRuleName}" + else + echo "domain:${normalizedInput}" + fi +} + #Add routing configuration addInstallRouting() { local tag=$1 # warp-socks local type=$2 # outboundTag/inboundTag local domain=$3 # Domain name + local rulePosition=$4 if [[ -z "${tag}" || -z "${type}" || -z "${domain}" ]]; then echoContent red " ---> Parameter error" @@ -5642,24 +5805,83 @@ EOF if echo "${routingRule}" | grep -q "${line}"; then echoContent yellow " ---> ${line} already exists, skip" else - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/v2fly/domain-list-community/contents/data/${line}" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - routingRule=$(echo "${routingRule}" | jq -r '.domain += ["geosite:'"${line}"'"]') - else - routingRule=$(echo "${routingRule}" | jq -r '.domain += ["domain:'"${line}"'"]') - fi + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + routingRule=$(echo "${routingRule}" | jq -r --arg rule "${matchedRuleValue}" '.domain += [$rule]') fi done < <(echo "${domain}" | tr ',' '\n') unInstallRouting "${tag}" "${type}" if ! grep -q "gstatic.com" ${configPath}09_routing.json && [[ "${tag}" == "blackhole-out" ]]; then local routing= - routing=$(jq -r ".routing.rules += [{\"type\": \"field\",\"domain\": [\"gstatic.com\"],\"outboundTag\": \"direct\"}]" ${configPath}09_routing.json) + routing=$(jq -r ".routing.rules += [{\"type\": \"field\",\"domain\": [\"domain:gstatic.com\"],\"outboundTag\": \"allow-domain-direct-out\"}]" ${configPath}09_routing.json) echo "${routing}" | jq . >${configPath}09_routing.json + + if [[ -f "${configPath}10_ipv4_outbounds.json" ]] && ! grep -q '"allow-domain-direct-out"' "${configPath}10_ipv4_outbounds.json"; then + local outbounds= + outbounds=$(jq -r '.outbounds += [{"protocol":"freedom","tag":"allow-domain-direct-out","settings":{"domainStrategy":"UseIP"}}]' ${configPath}10_ipv4_outbounds.json) + echo "${outbounds}" | jq . >${configPath}10_ipv4_outbounds.json + fi fi + if [[ "${rulePosition}" == "top" ]]; then + routing=$(jq -r ".routing.rules = [${routingRule}] + .routing.rules" ${configPath}09_routing.json) + else + routing=$(jq -r ".routing.rules += [${routingRule}]" ${configPath}09_routing.json) + fi + echo "${routing}" | jq . >${configPath}09_routing.json +} + +# Add Xray IP block routing +# Supports geoip:cn and custom IPv4/IPv6/CIDR input +addInstallIPRouting() { + + local tag=$1 + local type=$2 + local ipList=$3 + + if [[ -z "${tag}" || -z "${type}" || -z "${ipList}" ]]; then + echoContent red " ---> Parameter error" + exit 0 + fi + + if [[ ! -f "${configPath}09_routing.json" ]]; then + cat <${configPath}09_routing.json +{ + "routing":{ + "type": "field", + "rules": [] + } +} +EOF + fi + + local routingRule= + routingRule=$(jq -r ".routing.rules[]|select(.outboundTag==\"${tag}\" and (.protocol == null) and (.ip != null))" ${configPath}09_routing.json) + if [[ -z "${routingRule}" ]]; then + routingRule="{\"type\": \"field\",\"ip\": [],\"outboundTag\": \"${tag}\"}" + fi + + while read -r line; do + line=$(echo "${line}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + if [[ -z "${line}" ]]; then + continue + fi + + local ipRuleValue=${line} + if [[ "${line}" == "cn" ]]; then + ipRuleValue="geoip:cn" + fi + + if echo "${routingRule}" | grep -q "${ipRuleValue}"; then + echoContent yellow " ---> ${ipRuleValue} already exists, skip" + else + routingRule=$(echo "${routingRule}" | jq -r '.ip += ["'"${ipRuleValue}"'"]') + fi + done < <(echo "${ipList}" | tr ',' '\n') + + unInstallRouting "${tag}" "${type}" + local routing= routing=$(jq -r ".routing.rules += [${routingRule}]" ${configPath}09_routing.json) echo "${routing}" | jq . >${configPath}09_routing.json } @@ -6254,14 +6476,9 @@ EOF local domains= domains=[] while read -r line; do - local geositeStatus - geositeStatus=$(curl -s "https://api.github.com/repos/v2fly/domain-list-community/contents/data/${line}" | jq .message) - - if [[ "${geositeStatus}" == "null" ]]; then - domains=$(echo "${domains}" | jq -r '. += ["geosite:'"${line}"'"]') - else - domains=$(echo "${domains}" | jq -r '. += ["domain:'"${line}"'"]') - fi + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + domains=$(echo "${domains}" | jq -r --arg rule "${matchedRuleValue}" '. += [$rule]') done < <(echo "${domainList}" | tr ',' '\n') if [[ -f "${configPath}09_routing.json" ]]; then @@ -6427,7 +6644,9 @@ setUnlockSNI() { if [[ -n "${domainList}" ]]; then local hosts={} while read -r domain; do - hosts=$(echo "${hosts}" | jq -r ".\"geosite:${domain}\"=\"${setSNIP}\"") + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${domain}" "/etc/v2ray-agent/xray") + hosts=$(echo "${hosts}" | jq -r --arg key "${matchedRuleValue}" --arg value "${setSNIP}" '. + {($key):$value}') done < <(echo "${domainList}" | tr ',' '\n') cat <${configPath}11_dns.json { @@ -6462,6 +6681,16 @@ setUnlockDNS() { echoContent yellow "netflix,bahamut,hulu,hbo,disney,bbc,4chan,fox,abema,dmm,niconico,pixiv,bilibili,viu" read -r -p "Please enter the domain name according to the above example:" domainList if [[ "${domainList}" == "1" ]]; then + domainList="netflix,bahamut,hulu,hbo,disney,bbc,4chan,fox,abema,dmm,niconico,pixiv,bilibili,viu" + fi + if [[ -n "${domainList}" ]]; then + local domains=[] + while read -r line; do + local matchedRuleValue + matchedRuleValue=$(getDLCMatchedRuleValue "${line}" "/etc/v2ray-agent/xray") + domains=$(echo "${domains}" | jq -r --arg rule "${matchedRuleValue}" '. += [$rule]') + done < <(echo "${domainList}" | tr ',' '\n') + cat <${configPath}11_dns.json { "dns": { @@ -6469,39 +6698,7 @@ setUnlockDNS() { { "address": "${setDNS}", "port": 53, - "domains": [ - "geosite:netflix", - "geosite:bahamut", - "geosite:hulu", - "geosite:hbo", - "geosite:disney", - "geosite:bbc", - "geosite:4chan", - "geosite:fox", - "geosite:abema", - "geosite:dmm", - "geosite:niconico", - "geosite:pixiv", - "geosite:bilibili", - "geosite:viu" - ] - }, - "localhost" - ] - } -} -EOF - elif [[ -n "${domainList}" ]]; then - cat <${configPath}11_dns.json -{ - "dns": { - "servers": [ - { - "address": "${setDNS}", - "port": 53, - "domains": [ - "geosite:${domainList//,/\",\"geosite:}" - ] + "domains": ${domains} }, "localhost" ] @@ -7896,7 +8093,7 @@ menu() { cd "$HOME" || exit echoContent red "\n================================================ =================" echoContent green "Author: mack-a" - echoContent green "Current version: v2.10.20" + echoContent green "Current version: v3.5.13" echoContent green "Github: https://github.com/mack-a/v2ray-agent" echoContent green "Description: 8-in-1 coexistence script\c" showInstallStatus