mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-10 18:34:18 +08:00
603 lines
18 KiB
Bash
Executable File
603 lines
18 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
. /usr/lib/hijpass/app-logger.sh
|
|
|
|
log_debug "$0"
|
|
|
|
# 获取配置
|
|
DOWNLOAD_TIMEOUT=$(uci -q get hijpass.@rule[0].download_timeout || echo "30")
|
|
RETRY_COUNT=$(uci -q get hijpass.@rule[0].retry_count || echo "3")
|
|
|
|
# 文件路径
|
|
CHN_LIST_FILE=$(uci -q get hijpass.@hijpass[0].chn_list)
|
|
CHN_ROUTE_FILE=$(uci -q get hijpass.@hijpass[0].chn_route)
|
|
CHN_ROUTE6_FILE=$(uci -q get hijpass.@hijpass[0].chn_route6)
|
|
GFW_LIST_FILE=$(uci -q get hijpass.@hijpass[0].gfw_list)
|
|
GEO_SITE_FILE="/usr/share/v2ray/geosite.dat"
|
|
GEO_IP_FILE="/usr/share/v2ray/geoip.dat"
|
|
|
|
ENABLED=$(uci -q get hijpass.@hijpass[0].enabled || :)
|
|
|
|
LUA_SCRIPT="/usr/lib/hijpass/luci-utils.lua"
|
|
|
|
is_safe_query_arg() {
|
|
[ -n "$1" ] && echo "$1" | grep -Eq "^[A-Za-z0-9._:/-]{1,255}$" >/dev/null 2>&1
|
|
}
|
|
|
|
# 下载函数(单个文件)
|
|
download_file() {
|
|
local url output temp_file backup_file processed_file
|
|
url="$1"
|
|
output="$2"
|
|
temp_file="${output}.tmp"
|
|
backup_file="${output}.bak"
|
|
processed_file="${output}.processed"
|
|
|
|
log_info "Downloading from: $url"
|
|
|
|
# 备份现有文件
|
|
if [ -f "$output" ]; then
|
|
cp "$output" "$backup_file"
|
|
log_debug "Backed up existing file: $backup_file"
|
|
fi
|
|
|
|
# 使用curl下载
|
|
if curl -L --connect-timeout "$DOWNLOAD_TIMEOUT" \
|
|
--max-time "$((DOWNLOAD_TIMEOUT * 2))" \
|
|
-o "$temp_file" "$url" 2>/dev/null; then
|
|
|
|
# 检查文件是否有效(非空且大于100字节)
|
|
if [ -s "$temp_file" ] && [ "$(wc -c <"$temp_file")" -gt 100 ]; then
|
|
# 验证文件格式
|
|
if validate_file_format "$temp_file" "$output"; then
|
|
# 处理域名列表文件,只保留纯域名
|
|
if process_domain_file "$temp_file" "$processed_file" "$output"; then
|
|
mv "$processed_file" "$output"
|
|
rm -f "$backup_file" "$temp_file"
|
|
log_info "Successfully downloaded and processed: $output ($(wc -c <"$output") bytes)"
|
|
return 0
|
|
else
|
|
mv "$temp_file" "$output"
|
|
rm -f "$backup_file"
|
|
log_info "Successfully downloaded: $output ($(wc -c <"$output") bytes)"
|
|
return 0
|
|
fi
|
|
else
|
|
log_warn "Downloaded file format validation failed: $url"
|
|
# 恢复备份文件
|
|
if [ -f "$backup_file" ]; then
|
|
mv "$backup_file" "$output"
|
|
log_info "Restored backup file: $output"
|
|
fi
|
|
rm -f "$temp_file"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warn "Downloaded file is too small or empty: $url"
|
|
rm -f "$temp_file"
|
|
return 1
|
|
fi
|
|
else
|
|
log_warn "Failed to download: $url"
|
|
rm -f "$temp_file"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 下载多个URL并合并去重
|
|
download_and_merge_files() {
|
|
local urls output temp_file backup_file merged_file processed_file
|
|
urls="$1"
|
|
output="$2"
|
|
temp_file="${output}.tmp"
|
|
backup_file="${output}.bak"
|
|
merged_file="${output}.merged"
|
|
processed_file="${output}.processed"
|
|
|
|
log_info "Downloading and merging from multiple URLs..."
|
|
|
|
# 备份现有文件
|
|
if [ -f "$output" ]; then
|
|
cp "$output" "$backup_file"
|
|
log_debug "Backed up existing file: $backup_file"
|
|
fi
|
|
|
|
# 清空合并文件
|
|
: >"$merged_file"
|
|
|
|
local success_count=0
|
|
local total_count=0
|
|
|
|
# 下载所有URL
|
|
for _url in $urls; do
|
|
if [ -n "$_url" ]; then
|
|
total_count=$((total_count + 1))
|
|
local retry=0
|
|
local downloaded=0
|
|
|
|
while [ $retry -lt "$RETRY_COUNT" ]; do
|
|
if curl -L --connect-timeout "$DOWNLOAD_TIMEOUT" \
|
|
--max-time "$((DOWNLOAD_TIMEOUT * 2))" \
|
|
-o "$temp_file" "$_url" 2>/dev/null; then
|
|
|
|
# 检查文件是否有效
|
|
if [ -s "$temp_file" ] && [ "$(wc -c <"$temp_file")" -gt 100 ]; then
|
|
log_info "Successfully downloaded from: $_url"
|
|
cat "$temp_file" >>"$merged_file"
|
|
rm -f "$temp_file"
|
|
success_count=$((success_count + 1))
|
|
downloaded=1
|
|
break
|
|
fi
|
|
fi
|
|
retry=$((retry + 1))
|
|
if [ $retry -lt "$RETRY_COUNT" ]; then
|
|
log_warn "Retry $retry/$RETRY_COUNT for: $_url"
|
|
sleep 2
|
|
fi
|
|
done
|
|
|
|
if [ $downloaded -eq 0 ]; then
|
|
log_warn "Failed to download from: $_url after $RETRY_COUNT retries"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# 检查是否至少有一个URL下载成功
|
|
if [ $success_count -eq 0 ]; then
|
|
log_error "Failed to download from all URLs"
|
|
# 恢复备份文件
|
|
if [ -f "$backup_file" ]; then
|
|
mv "$backup_file" "$output"
|
|
log_info "Restored backup file: $output"
|
|
fi
|
|
rm -f "$merged_file" "$temp_file"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloaded from $success_count/$total_count URLs successfully"
|
|
|
|
# 处理合并后的文件(去重)
|
|
if process_domain_file "$merged_file" "$processed_file" "$output"; then
|
|
mv "$processed_file" "$output"
|
|
rm -f "$backup_file" "$merged_file" "$temp_file"
|
|
log_info "Successfully merged and processed: $output ($(wc -c <"$output") bytes)"
|
|
return 0
|
|
else
|
|
# 如果处理失败,直接使用合并文件
|
|
if [ -s "$merged_file" ]; then
|
|
# 对合并文件进行基础去重
|
|
sort "$merged_file" | uniq >"$output"
|
|
rm -f "$backup_file" "$merged_file" "$temp_file"
|
|
log_info "Successfully merged (without processing): $output ($(wc -c <"$output") bytes)"
|
|
return 0
|
|
else
|
|
log_error "Merged file is empty"
|
|
if [ -f "$backup_file" ]; then
|
|
mv "$backup_file" "$output"
|
|
log_info "Restored backup file: $output"
|
|
fi
|
|
rm -f "$merged_file" "$temp_file"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# 检测文件格式类型
|
|
detect_file_format() {
|
|
local file sample_lines
|
|
file="$1"
|
|
|
|
# 抽取前10行非空非注释行进行格式检测
|
|
sample_lines=$(grep -v "^#\|^!\|^$" "$file" | head -n 10)
|
|
|
|
if echo "$sample_lines" | grep -q "^server=/"; then
|
|
echo "dnsmasq"
|
|
elif echo "$sample_lines" | grep -q "^+\."; then
|
|
echo "adguard"
|
|
elif echo "$sample_lines" | grep -q "^@@||.*\^"; then
|
|
echo "adblock"
|
|
elif echo "$sample_lines" | grep -q "^domain:\|^full:\|^regexp:\|^keyword:"; then
|
|
echo "v2ray"
|
|
elif echo "$sample_lines" | grep -qE "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
|
|
echo "plain"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
}
|
|
|
|
# 处理域名文件,只保留纯域名
|
|
process_domain_file() {
|
|
local input_file output_file original_output format
|
|
input_file="$1"
|
|
output_file="$2"
|
|
original_output="$3"
|
|
|
|
# 检查是否为域名列表文件
|
|
case "$original_output" in
|
|
*"gfw"* | *"chn-list"* | *"china"* | *"accelerated-domains"*)
|
|
log_debug "Processing domain file: $original_output"
|
|
|
|
# 检测文件格式
|
|
format=$(detect_file_format "$input_file")
|
|
log_debug "Detected file format: $format"
|
|
|
|
# 根据格式选择批量处理方式
|
|
case "$format" in
|
|
"dnsmasq")
|
|
# 批量处理dnsmasq格式: server=/domain.com/8.8.8.8
|
|
grep "^server=/" "$input_file" |
|
|
sed 's/server=\/\([^\/]*\)\/.*/\1/' |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
"adguard")
|
|
# 批量处理AdGuard Home格式: +.domain.com
|
|
grep "^+\." "$input_file" |
|
|
sed 's/^+\.//' |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
"adblock")
|
|
# 批量处理AdBlock格式: @@||domain.com^
|
|
grep "^@@||.*\^" "$input_file" |
|
|
sed 's/^@@||\([^|^]*\)\^.*/\1/' |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
"v2ray")
|
|
# 批量处理V2Ray规则格式,只保留domain和full类型
|
|
grep "^domain:\|^full:" "$input_file" |
|
|
sed 's/^[^:]*://' |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
"plain")
|
|
# 批量处理纯域名格式
|
|
grep -v "^#\|^!\|^$" "$input_file" |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
"unknown")
|
|
# 混合格式,使用通用处理方式
|
|
{
|
|
# dnsmasq格式
|
|
grep "^server=/" "$input_file" | sed 's/server=\/\([^\/]*\)\/.*/\1/'
|
|
# AdGuard Home格式
|
|
grep "^+\." "$input_file" | sed 's/^+\.//'
|
|
# AdBlock格式
|
|
grep "^@@||.*\^" "$input_file" | sed 's/^@@||\([^|^]*\)\^.*/\1/'
|
|
# V2Ray规则格式
|
|
grep "^domain:\|^full:" "$input_file" | sed 's/^[^:]*://'
|
|
# 纯域名格式
|
|
grep -v "^#\|^!\|^$\|^server=\|^+\.\|^@@||\|^domain:\|^full:\|^regexp:\|^keyword:" "$input_file"
|
|
} |
|
|
grep -E "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$" |
|
|
sort | uniq >"$output_file"
|
|
;;
|
|
esac
|
|
|
|
# 检查处理后的文件是否有内容
|
|
if [ -s "$output_file" ]; then
|
|
local domain_count
|
|
domain_count=$(wc -l <"$output_file")
|
|
log_info "Processed domain file ($format format): extracted $domain_count unique domains"
|
|
return 0
|
|
else
|
|
log_warn "No valid domains extracted from file"
|
|
rm -f "$output_file"
|
|
return 1
|
|
fi
|
|
;;
|
|
*)
|
|
# 非域名文件,不需要处理
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 验证文件格式
|
|
validate_file_format() {
|
|
local file output
|
|
file="$1"
|
|
output="$2"
|
|
|
|
case "$output" in
|
|
*"chn-list"* | *"china"* | *"accelerated-domains"*)
|
|
# 验证域名列表格式
|
|
if head -n 5 "$file" | grep -q "server="; then
|
|
return 0
|
|
elif head -n 5 "$file" | grep -qE "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
|
|
return 0
|
|
fi
|
|
;;
|
|
*"gfw"*)
|
|
# 验证GFW域名列表格式
|
|
if head -n 5 "$file" | grep -qE "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
|
|
log_debug "GFW file validated: plain domain format"
|
|
return 0
|
|
elif head -n 5 "$file" | grep -q "server="; then
|
|
log_debug "GFW file validated: dnsmasq format"
|
|
return 0
|
|
elif head -n 5 "$file" | grep -qE "^\+\.[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
|
|
log_debug "GFW file validated: AdGuard Home format"
|
|
return 0
|
|
elif head -n 5 "$file" | grep -qE "^@@\|\|[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\^$"; then
|
|
log_debug "GFW file validated: AdBlock format"
|
|
return 0
|
|
elif head -n 5 "$file" | grep -qE "^(domain|full|regexp|keyword):"; then
|
|
log_debug "GFW file validated: v2ray rules format"
|
|
return 0
|
|
fi
|
|
;;
|
|
*"geo"*".dat")
|
|
# 验证GEO数据库文件(二进制文件)
|
|
if file "$file" | grep -q "data"; then
|
|
return 0
|
|
fi
|
|
;;
|
|
*"route"* | *"ip"*)
|
|
# 验证IP路由格式
|
|
if head -n 5 "$file" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$"; then
|
|
return 0
|
|
elif head -n 5 "$file" | grep -qE "^[0-9a-fA-F:]+(/[0-9]+)?$"; then
|
|
return 0
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
log_warn "File format validation failed for: $file"
|
|
return 1
|
|
}
|
|
|
|
# 下载CHN List
|
|
download_chn_list() {
|
|
if [ -z "$CHN_LIST_FILE" ]; then
|
|
log_warn "CHN list file path not configured"
|
|
return 1
|
|
fi
|
|
local chn_domain_list
|
|
chn_domain_list="$(uci -q get hijpass.@rule[0].chn_domain_list || :)"
|
|
if [ -z "$chn_domain_list" ]; then
|
|
log_warn "No CHN list download URLs configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading CHN list..."
|
|
|
|
# 下载所有URL并合并去重
|
|
if download_and_merge_files "$chn_domain_list" "$CHN_LIST_FILE"; then
|
|
log_info "CHN list downloaded and merged successfully"
|
|
return 0
|
|
else
|
|
log_error "Failed to download CHN list from all URLs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 下载CHN Route IPv4
|
|
download_chn_route_ipv4() {
|
|
if [ -z "$CHN_ROUTE_FILE" ]; then
|
|
log_warn "CHN route IPv4 file path not configured"
|
|
return 1
|
|
fi
|
|
local chn_v4_route
|
|
chn_v4_route=$(uci -q get hijpass.@rule[0].chn_v4_route)
|
|
if [ -z "$chn_v4_route" ]; then
|
|
log_warn "No CHN route IPv4 download URLs configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading CHN route IPv4..."
|
|
|
|
# 下载所有URL并合并去重
|
|
if download_and_merge_files "$chn_v4_route" "$CHN_ROUTE_FILE"; then
|
|
log_info "CHN route IPv4 downloaded and merged successfully"
|
|
return 0
|
|
else
|
|
log_error "Failed to download CHN route IPv4 from all URLs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 下载CHN Route IPv6
|
|
download_chn_route_ipv6() {
|
|
if [ -z "$CHN_ROUTE6_FILE" ]; then
|
|
log_warn "CHN route IPv6 file path not configured"
|
|
return 1
|
|
fi
|
|
|
|
local chn_v6_route
|
|
chn_v6_route=$(uci -q get hijpass.@rule[0].chn_v6_route)
|
|
if [ -z "$chn_v6_route" ]; then
|
|
log_warn "No CHN route IPv6 download URLs configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading CHN route IPv6..."
|
|
|
|
# 下载所有URL并合并去重
|
|
if download_and_merge_files "$chn_v6_route" "$CHN_ROUTE6_FILE"; then
|
|
log_info "CHN route IPv6 downloaded and merged successfully"
|
|
return 0
|
|
else
|
|
log_error "Failed to download CHN route IPv6 from all URLs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# 下载GeoSite
|
|
download_geosite() {
|
|
local url retry
|
|
url=$(uci -q get hijpass.@rule[0].geosite)
|
|
if [ -z "$url" ]; then
|
|
log_warn "GeoSite download URL not configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading GeoSite..."
|
|
|
|
retry=0
|
|
while [ $retry -lt "$RETRY_COUNT" ]; do
|
|
if download_file "$url" "$GEO_SITE_FILE"; then
|
|
log_info "GeoSite downloaded successfully"
|
|
return 0
|
|
fi
|
|
retry=$((retry + 1))
|
|
log_warn "Retry $retry/$RETRY_COUNT for GeoSite"
|
|
sleep 2
|
|
done
|
|
|
|
log_error "Failed to download GeoSite"
|
|
return 1
|
|
}
|
|
|
|
# 下载GeoIP
|
|
download_geoip() {
|
|
local url retry
|
|
url=$(uci -q get hijpass.@rule[0].geoip)
|
|
if [ -z "$url" ]; then
|
|
log_warn "GeoIP download URL not configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading GeoIP..."
|
|
|
|
retry=0
|
|
while [ $retry -lt "$RETRY_COUNT" ]; do
|
|
if download_file "$url" "$GEO_IP_FILE"; then
|
|
log_info "GeoIP downloaded successfully"
|
|
return 0
|
|
fi
|
|
retry=$((retry + 1))
|
|
log_warn "Retry $retry/$RETRY_COUNT for GeoIP"
|
|
sleep 2
|
|
done
|
|
|
|
log_error "Failed to download GeoIP"
|
|
return 1
|
|
}
|
|
|
|
# 下载GFW
|
|
download_gfw_list() {
|
|
GFW_LIST_FILE=$(uci -q get hijpass.@hijpass[0].gfw_list)
|
|
if [ -z "$GFW_LIST_FILE" ]; then
|
|
log_warn "GFW list file path not configured"
|
|
return 1
|
|
fi
|
|
local urls
|
|
urls=$(uci -q get hijpass.@rule[0].gfw_list)
|
|
if [ -z "$urls" ]; then
|
|
log_warn "GFW download URLs not configured"
|
|
return 1
|
|
fi
|
|
|
|
log_info "Downloading GFW list..."
|
|
|
|
# 下载所有URL并合并去重
|
|
if download_and_merge_files "$urls" "$GFW_LIST_FILE"; then
|
|
log_info "GFW list downloaded and merged successfully"
|
|
return 0
|
|
else
|
|
log_error "Failed to download GFW list from all URLs"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
query_rule() {
|
|
if lua $LUA_SCRIPT iptype "$1" >/dev/null 2>&1; then
|
|
find "/etc/hijpass/rules/" -type f \( -name "chn-route*" -o -name "ip*" \) | while read -r file; do
|
|
if lua $LUA_SCRIPT ipmatch "$1" -f "$file" >/dev/null 2>&1; then
|
|
basename "$file"
|
|
fi
|
|
done | sort -u
|
|
else
|
|
find "/etc/hijpass/rules/" -type f \( -name "*list.txt" -o -name "domain*" \) | while read -r file; do
|
|
if lua $LUA_SCRIPT domainmatch "$1" "$file" >/dev/null 2>&1; then
|
|
basename "$file"
|
|
fi
|
|
done | sort -u
|
|
fi
|
|
}
|
|
|
|
update_rule_files() {
|
|
local failed=0
|
|
|
|
log_info "Update all rule files..."
|
|
download_gfw_list || failed=1
|
|
download_chn_list || failed=1
|
|
download_chn_route_ipv4 || failed=1
|
|
download_chn_route_ipv6 || failed=1
|
|
download_geosite || failed=1
|
|
download_geoip || failed=1
|
|
|
|
if [ "$ENABLED" != "1" ]; then
|
|
log_info "Skip restart hijpass"
|
|
elif ! /etc/init.d/hijpass restart; then
|
|
failed=1
|
|
fi
|
|
|
|
return "$failed"
|
|
}
|
|
|
|
# 主函数
|
|
main() {
|
|
local action="$1"
|
|
|
|
case "$action" in
|
|
"chnlist")
|
|
download_chn_list
|
|
;;
|
|
"gfw")
|
|
download_gfw_list
|
|
;;
|
|
"chnroute")
|
|
download_chn_route_ipv4
|
|
download_chn_route_ipv6
|
|
;;
|
|
"geo")
|
|
download_geosite
|
|
download_geoip
|
|
;;
|
|
"all")
|
|
log_info "Downloading all rule files..."
|
|
download_gfw_list
|
|
download_chn_list
|
|
download_chn_route_ipv4
|
|
download_chn_route_ipv6
|
|
download_geosite
|
|
download_geoip
|
|
;;
|
|
"update")
|
|
if update_rule_files; then
|
|
printf '{"success":true}\n'
|
|
else
|
|
printf '{"success":false}\n'
|
|
return 1
|
|
fi
|
|
;;
|
|
"query")
|
|
if ! is_safe_query_arg "$2"; then
|
|
echo "Invalid query" >&2
|
|
exit 1
|
|
fi
|
|
query_rule "$2"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 [chnlist|chnroute|geo|all|update|query]"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# 创建必要的目录
|
|
mkdir -p "$(dirname "$CHN_LIST_FILE")" 2>/dev/null
|
|
mkdir -p "$(dirname "$CHN_ROUTE_FILE")" 2>/dev/null
|
|
mkdir -p "$(dirname "$CHN_ROUTE6_FILE")" 2>/dev/null
|
|
mkdir -p "$(dirname "$GEO_SITE_FILE")" 2>/dev/null
|
|
mkdir -p "$(dirname "$GEO_IP_FILE")" 2>/dev/null
|
|
mkdir -p "$(dirname "$GFW_LIST_FILE")" 2>/dev/null
|
|
|
|
# 执行主函数
|
|
main "$@"
|