This commit is contained in:
sindricn
2025-10-01 10:12:28 +08:00
parent 86167c14f4
commit f08d189d13
4 changed files with 388 additions and 108 deletions
+4 -1
View File
@@ -64,7 +64,10 @@
"Bash(check_rule_type_conflict:*)",
"Bash(apply_outbound_rule)",
"Bash(check_existing_outbound_type:*)",
"SlashCommand(/sc:analyze:*)"
"SlashCommand(/sc:analyze:*)",
"Bash(test_dns_resolution:*)",
"Bash(echo $?)",
"Bash(git checkout:*)"
],
"deny": []
}
+2 -2
View File
@@ -109,7 +109,7 @@ check_script_integrity() {
# 检查必需的脚本文件
local required_scripts=(
"install.sh"
# "install.sh" (在根目录)
"config.sh"
"service.sh"
"domain-test.sh"
@@ -262,7 +262,7 @@ wait_for_user() {
install_hysteria() {
log_info "准备安装 Hysteria2..."
if safe_source_script "$SCRIPTS_DIR/install.sh" "安装脚本"; then
if safe_source_script "$SCRIPT_DIR/install.sh" "安装脚本"; then
install_hysteria2
fi
wait_for_user
+108 -30
View File
@@ -119,42 +119,89 @@ detect_network_quality() {
fi
}
# 测试单个域名延迟(优化版本)
# DNS解析验证函数
test_dns_resolution() {
local domain=$1
local timeout_val=${2:-3}
# 使用dig验证DNS解析(优先)
if command -v dig >/dev/null 2>&1; then
if timeout "$timeout_val" dig +short "$domain" A >/dev/null 2>&1; then
return 0
fi
fi
# 使用nslookup作为备用
if command -v nslookup >/dev/null 2>&1; then
if timeout "$timeout_val" nslookup "$domain" >/dev/null 2>&1; then
return 0
fi
fi
# 使用host作为最后备用
if command -v host >/dev/null 2>&1; then
if timeout "$timeout_val" host "$domain" >/dev/null 2>&1; then
return 0
fi
fi
return 1
}
# 测试单个域名延迟(优化版本 + DNS验证)
test_domain_latency() {
local domain=$1
local timeout_val=${2:-3}
local attempts=${3:-2} # 默认尝试2次
local results=()
# 检查缓存
local cache_file=$(get_cache_file "$domain")
if is_cache_valid "$cache_file"; then
read_from_cache "$cache_file"
return 0
local cached_result=$(read_from_cache "$cache_file")
# 验证缓存结果是否包含DNS验证状态
if [[ "$cached_result" =~ dns_ok|dns_fail ]]; then
echo "$cached_result"
return 0
fi
fi
# 多次测试取最佳结果
# 首先验证DNS解析
local dns_status="dns_fail"
if test_dns_resolution "$domain" "$timeout_val"; then
dns_status="dns_ok"
else
# DNS解析失败,直接返回失败状态
local result="9999 $domain $dns_status"
write_to_cache "$cache_file" "$result"
return 1
fi
# 多次测试取最佳结果(只有DNS解析成功才测试连接)
for ((i=1; i<=attempts; i++)); do
local start_time=$(date +%s%3N)
if timeout "$timeout_val" openssl s_client -connect "$domain:443" -servername "$domain" </dev/null >/dev/null 2>&1; then
local end_time=$(date +%s%3N)
local latency=$((end_time - start_time))
results+=($latency)
fi
done
# 如果有成功的测试结果,返回最小值
if [[ ${#results[@]} -gt 0 ]]; then
local min_latency=$(printf '%s\n' "${results[@]}" | sort -n | head -1)
local result="$min_latency $domain"
local result="$min_latency $domain $dns_status"
# 写入缓存
write_to_cache "$cache_file" "$result"
echo "$result"
return 0
fi
# SSL连接失败但DNS解析成功
local result="9999 $domain $dns_status"
write_to_cache "$cache_file" "$result"
return 1
}
@@ -187,14 +234,15 @@ test_all_domains_concurrent_silent() {
# 等待所有任务完成
wait
# 返回排序结果
# 返回排序结果(过滤DNS失败的域名)
if [[ -s "$results_file" ]]; then
sort -n "$results_file"
# 只返回DNS解析成功且延迟不是9999的结果
awk '$1 != 9999 && $3 == "dns_ok" {print $1 " " $2 " " $3}' "$results_file" | sort -n
local exit_code=0
else
local exit_code=1
fi
rm -f "$results_file"
return $exit_code
}
@@ -253,14 +301,15 @@ test_all_domains_concurrent() {
echo -e "${GREEN}测试完成!${NC}" >&2
echo "" >&2
# 返回排序结果
# 返回排序结果(过滤DNS失败的域名)
if [[ -s "$results_file" ]]; then
sort -n "$results_file"
# 只返回DNS解析成功且延迟不是9999的结果
awk '$1 != 9999 && $3 == "dns_ok" {print $1 " " $2 " " $3}' "$results_file" | sort -n
local exit_code=0
else
local exit_code=1
fi
# 清理临时文件
rm -f "$results_file" "$progress_file"
return $exit_code
@@ -281,11 +330,11 @@ show_test_results() {
return 1
fi
printf "%-5s %-30s %-8s %s\n" "排名" "域名" "延迟(ms)" "评级"
echo "------------------------------------------------"
printf "%-5s %-30s %-8s %-8s %s\n" "排名" "域名" "延迟(ms)" "DNS状态" "评级"
echo "---------------------------------------------------------"
local rank=1
echo "$results" | head -n 10 | while read -r latency domain; do
echo "$results" | head -n 10 | while read -r latency domain dns_status; do
# 延迟评级
local rating
if [[ $latency -lt 50 ]]; then
@@ -300,7 +349,15 @@ show_test_results() {
rating="${RED}较差${NC}"
fi
printf "%-5d %-30s %-8d %b\n" "$rank" "$domain" "$latency" "$rating"
# DNS状态显示
local dns_display
if [[ "$dns_status" == "dns_ok" ]]; then
dns_display="${GREEN}正常${NC}"
else
dns_display="${RED}失败${NC}"
fi
printf "%-5d %-30s %-8d %-8s %b\n" "$rank" "$domain" "$latency" "$dns_display" "$rating"
rank=$((rank + 1))
done
}
@@ -310,7 +367,13 @@ get_best_domain() {
local best_result=$(test_all_domains_concurrent_silent | head -n 1)
if [[ -n "$best_result" ]]; then
local domain=$(echo "$best_result" | awk '{print $2}')
echo "https://$domain/"
local dns_status=$(echo "$best_result" | awk '{print $3}')
# 确保DNS解析成功
if [[ "$dns_status" == "dns_ok" ]]; then
echo "https://$domain/"
else
echo "https://news.ycombinator.com/"
fi
else
echo "https://news.ycombinator.com/"
fi
@@ -320,7 +383,14 @@ get_best_domain() {
get_best_domain_name() {
local best_result=$(test_all_domains_concurrent_silent | head -n 1)
if [[ -n "$best_result" ]]; then
echo "$best_result" | awk '{print $2}'
local domain=$(echo "$best_result" | awk '{print $2}')
local dns_status=$(echo "$best_result" | awk '{print $3}')
# 确保DNS解析成功
if [[ "$dns_status" == "dns_ok" ]]; then
echo "$domain"
else
echo "cdn.jsdelivr.net"
fi
else
echo "cdn.jsdelivr.net"
fi
@@ -400,8 +470,9 @@ interactive_domain_selection() {
if [[ -n "$line" ]]; then
local latency=$(echo "$line" | awk '{print $1}')
local domain=$(echo "$line" | awk '{print $2}')
local dns_status=$(echo "$line" | awk '{print $3}')
if [[ "$latency" =~ ^[0-9]+$ ]] && [[ -n "$domain" ]] && [[ ! "$domain" =~ [[:space:]] ]]; then
if [[ "$latency" =~ ^[0-9]+$ ]] && [[ -n "$domain" ]] && [[ ! "$domain" =~ [[:space:]] ]] && [[ "$dns_status" == "dns_ok" ]]; then
# 延迟评级
local rating
if [[ $latency -lt 100 ]]; then
@@ -411,7 +482,7 @@ interactive_domain_selection() {
else
rating="${RED}一般${NC}"
fi
printf "%-5d %-30s %-8d %b\n" "$index" "$domain" "$latency" "$rating"
domains_array+=("$domain")
index=$((index + 1))
@@ -544,8 +615,8 @@ test_custom_domains() {
echo ""
echo -e "${BLUE}开始并发测试 ${#custom_domains[@]} 个自定义域名...${NC}"
echo ""
printf "%-30s %-10s %s\n" "域名" "延迟(ms)" "状态"
echo "------------------------------------------------"
printf "%-30s %-10s %-8s %s\n" "域名" "延迟(ms)" "DNS状态" "状态"
echo "-------------------------------------------------------"
local results_file=$(mktemp)
local network_quality=$(detect_network_quality)
@@ -560,10 +631,17 @@ test_custom_domains() {
(
if result=$(test_domain_latency "$domain" "$timeout_val" 1); then
local latency=$(echo "$result" | awk '{print $1}')
printf "%-30s %-10d %s\n" "$domain" "$latency" "${GREEN}成功${NC}"
local dns_status=$(echo "$result" | awk '{print $3}')
local dns_display
if [[ "$dns_status" == "dns_ok" ]]; then
dns_display="${GREEN}正常${NC}"
else
dns_display="${RED}失败${NC}"
fi
printf "%-30s %-10d %-8s %s\n" "$domain" "$latency" "$dns_display" "${GREEN}成功${NC}"
echo "$result" >> "$results_file"
else
printf "%-30s %-10s %s\n" "$domain" "-" "${RED}失败${NC}"
printf "%-30s %-10s %-8s %s\n" "$domain" "-" "${RED}失败${NC}" "${RED}失败${NC}"
fi
) &
done
+274 -75
View File
@@ -2820,54 +2820,200 @@ update_rule_config_value() {
fi
}
# 5. 删除出站规则
# 获取配置文件中的所有出站规则名称
get_config_outbound_rules() {
if [[ ! -f "$HYSTERIA_CONFIG" ]]; then
return 1
fi
# 提取配置文件中所有的 outbound 规则名称
awk '
/^[[:space:]]*outbound:[[:space:]]*$/ { in_outbound = 1; next }
in_outbound && /^[[:space:]]*[a-zA-Z]+:[[:space:]]*$/ && !/^[[:space:]]*(outbound|transport|auth|masquerade|bandwidth):/ { in_outbound = 0 }
in_outbound && /^[[:space:]]*-[[:space:]]*name:[[:space:]]*(.+)$/ {
match($0, /name:[[:space:]]*([^[:space:]]+)/, arr)
if (arr[1]) print arr[1]
}
' "$HYSTERIA_CONFIG" 2>/dev/null
}
# 从配置文件中删除指定的出站规则
remove_rule_from_config() {
local rule_name="$1"
if [[ ! -f "$HYSTERIA_CONFIG" ]]; then
log_error "配置文件不存在"
return 1
fi
local temp_config="/tmp/hysteria_delete_config_$$_$(date +%s).yaml"
local in_target_rule=false
local rule_found=false
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*name:[[:space:]]*${rule_name}[[:space:]]*$ ]]; then
in_target_rule=true
rule_found=true
continue
elif [[ "$in_target_rule" == true ]]; then
# 检查是否到达下一个规则或段落
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*name: ]] || [[ "$line" =~ ^[[:space:]]*[a-zA-Z]+:[[:space:]]*$ ]] && [[ ! "$line" =~ ^[[:space:]]*(type|direct|socks5|http|addr|url|mode|username|password|insecure): ]]; then
in_target_rule=false
echo "$line" >> "$temp_config"
fi
# 在目标规则中的行都跳过
else
echo "$line" >> "$temp_config"
fi
done < "$HYSTERIA_CONFIG"
if [[ "$rule_found" == true ]]; then
if safe_move_config "$temp_config" "$HYSTERIA_CONFIG"; then
log_success "已从配置文件中删除规则 '$rule_name'"
return 0
else
log_error "配置文件更新失败"
rm -f "$temp_config"
return 1
fi
else
log_warn "在配置文件中未找到规则 '$rule_name'"
rm -f "$temp_config"
return 1
fi
}
# 5. 删除出站规则 (增强版)
delete_outbound_rule_new() {
init_rules_library
echo -e "${BLUE}=== 删除出站规则 ===${NC}"
echo ""
# 列出规则库中的规则 - 使用可靠的grep方法
local rules=()
local rule_count=0
# 收集规则库中的规则
local library_rules=()
while IFS= read -r rule_name; do
if [[ -n "$rule_name" ]]; then
rules+=("$rule_name")
((rule_count++))
# 检查是否已应用 - 只根据配置文件实际状态
local status="❌ 未应用"
if [[ -f "$HYSTERIA_CONFIG" ]] && grep -q "name:[[:space:]]*[\"']*${rule_name}[\"']*[[:space:]]*$" "$HYSTERIA_CONFIG" 2>/dev/null; then
status="✅ 已应用"
fi
echo "$rule_count. $rule_name $status"
library_rules+=("$rule_name")
fi
done < <(grep -o "^[[:space:]]\{2\}[a-zA-Z_][a-zA-Z0-9_]*:" "$RULES_LIBRARY" | sed 's/^[[:space:]]\{2\}\([^:]*\):.*/\1/')
if [[ ${#rules[@]} -eq 0 ]]; then
echo -e "${YELLOW}没有可删除的规则${NC}"
# 收集配置文件中的规则
local config_rules=()
while IFS= read -r rule_name; do
if [[ -n "$rule_name" ]]; then
config_rules+=("$rule_name")
fi
done < <(get_config_outbound_rules)
# 合并去重规则列表
local all_rules=()
local rule_sources=() # 记录规则来源: library/config/both
local rule_count=0
# 添加规则库中的规则
for rule in "${library_rules[@]}"; do
all_rules+=("$rule")
rule_sources+=("library")
((rule_count++))
done
# 添加配置文件中独有的规则
for rule in "${config_rules[@]}"; do
local found_in_library=false
for lib_rule in "${library_rules[@]}"; do
if [[ "$rule" == "$lib_rule" ]]; then
found_in_library=true
# 更新来源为both
for i in "${!all_rules[@]}"; do
if [[ "${all_rules[i]}" == "$rule" ]]; then
rule_sources[i]="both"
break
fi
done
break
fi
done
if [[ "$found_in_library" == false ]]; then
all_rules+=("$rule")
rule_sources+=("config")
((rule_count++))
fi
done
if [[ ${#all_rules[@]} -eq 0 ]]; then
echo -e "${YELLOW}没有找到任何规则${NC}"
wait_for_user
return
fi
echo -e "${CYAN}找到以下规则:${NC}"
echo ""
read -p "请选择要删除的规则 [1-$rule_count]: " choice
printf "%-5s %-25s %-12s %s\n" "编号" "规则名称" "位置" "状态"
echo "---------------------------------------------------"
for i in "${!all_rules[@]}"; do
local rule_name="${all_rules[i]}"
local source="${rule_sources[i]}"
local status=""
# 确定位置显示
local location_display
case "$source" in
"library") location_display="${GREEN}规则库${NC}" ;;
"config") location_display="${YELLOW}配置文件${NC}" ;;
"both") location_display="${BLUE}规则库+配置${NC}" ;;
esac
# 检查应用状态
if [[ -f "$HYSTERIA_CONFIG" ]] && grep -q "name:[[:space:]]*[\"']*${rule_name}[\"']*[[:space:]]*$" "$HYSTERIA_CONFIG" 2>/dev/null; then
status="✅ 已应用"
else
status="❌ 未应用"
fi
printf "%-5d %-25s %-12s %s\n" "$((i+1))" "$rule_name" "$location_display" "$status"
done
echo ""
echo -e "${YELLOW}说明:${NC}"
echo "• 规则库: 规则模板,可重复应用"
echo "• 配置文件: 当前活动的规则"
echo "• 规则库+配置: 存在于两个位置"
echo ""
read -p "请选择要删除的规则编号 [1-$rule_count]: " choice
if [[ ! "$choice" =~ ^[0-9]+$ ]] || [[ "$choice" -lt 1 ]] || [[ "$choice" -gt $rule_count ]]; then
log_error "无效选择"
return 1
fi
local selected_rule="${rules[$((choice-1))]}"
local selected_rule="${all_rules[$((choice-1))]}"
local selected_source="${rule_sources[$((choice-1))]}"
echo ""
echo -e "${RED}⚠️ 警告: 即将删除规则 '$selected_rule'${NC}"
# 检查是否已应用
if grep -q "- $selected_rule" "$RULES_STATE" 2>/dev/null; then
echo -e "${YELLOW}此规则当前已应用,删除将同时从配置文件中移除${NC}"
fi
# 根据规则位置给出不同的提示
case "$selected_source" in
"library")
echo -e "${YELLOW}此规则仅存在于规则库中${NC}"
;;
"config")
echo -e "${YELLOW}此规则仅存在于配置文件中,删除后将立即生效${NC}"
;;
"both")
echo -e "${YELLOW}此规则同时存在于规则库和配置文件中${NC}"
echo "选择删除范围:"
echo "1. 仅从规则库中删除"
echo "2. 仅从配置文件中删除"
echo "3. 同时从两个位置删除"
echo ""
read -p "请选择 [1-3]: " delete_scope
;;
esac
echo ""
read -p "确认删除? [y/N]: " confirm
@@ -2877,57 +3023,95 @@ delete_outbound_rule_new() {
return 0
fi
# 如果已应用,先从配置中移除
if grep -q "- $selected_rule" "$RULES_STATE" 2>/dev/null; then
echo "正在从配置文件中移除..."
local delete_success=false
# 从配置文件中删除
if [[ -f "$HYSTERIA_CONFIG" ]]; then
local temp_config="/tmp/hysteria_delete_$$_$(date +%s).yaml"
local in_target_rule=false
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*name:[[:space:]]*${selected_rule}[[:space:]]*$ ]]; then
in_target_rule=true
continue
elif [[ "$in_target_rule" == true ]]; then
if [[ "$line" =~ ^[[:space:]]*-[[:space:]]*name: ]] || [[ "$line" =~ ^[[:space:]]*[a-zA-Z]+:[[:space:]]*$ ]] && [[ ! "$line" =~ ^[[:space:]]*(type|direct|socks5|http|addr|url|mode|username|password|insecure): ]]; then
in_target_rule=false
echo "$line" >> "$temp_config"
fi
# 在规则中的行都跳过
else
echo "$line" >> "$temp_config"
fi
done < "$HYSTERIA_CONFIG"
safe_move_config "$temp_config" "$HYSTERIA_CONFIG"
fi
# 从状态文件中移除
local temp_state="${RULES_STATE}.tmp"
if awk -v rule="$selected_rule" '
$0 == " - " rule { next }
{ print }
' "$RULES_STATE" > "$temp_state" 2>/dev/null; then
if [[ -s "$temp_state" ]]; then
mv "$temp_state" "$RULES_STATE" 2>/dev/null || rm -f "$temp_state"
else
rm -f "$temp_state"
# 执行删除操作
case "$selected_source" in
"library")
if delete_rule_from_library "$selected_rule"; then
delete_success=true
fi
;;
"config")
if remove_rule_from_config "$selected_rule"; then
delete_success=true
# 从状态文件中移除
remove_rule_from_state "$selected_rule"
fi
;;
"both")
case "$delete_scope" in
1)
if delete_rule_from_library "$selected_rule"; then
delete_success=true
fi
;;
2)
if remove_rule_from_config "$selected_rule"; then
remove_rule_from_state "$selected_rule"
delete_success=true
fi
;;
3)
local lib_success=false
local config_success=false
if delete_rule_from_library "$selected_rule"; then
lib_success=true
fi
if remove_rule_from_config "$selected_rule"; then
remove_rule_from_state "$selected_rule"
config_success=true
fi
if [[ "$lib_success" == true ]] || [[ "$config_success" == true ]]; then
delete_success=true
fi
;;
*)
log_error "无效的删除范围选择"
return 1
;;
esac
;;
esac
if [[ "$delete_success" == true ]]; then
log_success "规则 '$selected_rule' 删除操作完成"
# 如果删除了配置文件中的规则,询问是否重启服务
if [[ "$selected_source" == "config" ]] || [[ "$selected_source" == "both" && ("$delete_scope" == "2" || "$delete_scope" == "3") ]]; then
echo ""
read -p "是否重启 Hysteria2 服务以应用更改? [y/N]: " restart_service
if [[ $restart_service =~ ^[Yy]$ ]]; then
if systemctl restart hysteria-server 2>/dev/null; then
log_success "服务已重启"
else
log_warn "服务重启失败,请手动重启"
fi
fi
else
rm -f "$temp_state"
fi
else
log_error "规则删除失败"
return 1
fi
# 从规则库中删除
local temp_library="/tmp/rules_delete_$$_$(date +%s).yaml"
wait_for_user
}
# 从规则库中删除规则的辅助函数
delete_rule_from_library() {
local rule_name="$1"
local temp_library="/tmp/rules_delete_library_$$_$(date +%s).yaml"
local in_target_rule=false
local rule_indent=""
local rule_found=false
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$line" =~ ^[[:space:]]*${selected_rule}:[[:space:]]*$ ]]; then
if [[ "$line" =~ ^[[:space:]]*${rule_name}:[[:space:]]*$ ]]; then
in_target_rule=true
rule_found=true
rule_indent=$(echo "$line" | sed 's/[a-zA-Z].*//')
continue
elif [[ "$in_target_rule" == true ]]; then
@@ -2948,24 +3132,39 @@ delete_outbound_rule_new() {
fi
done < "$RULES_LIBRARY"
if mv "$temp_library" "$RULES_LIBRARY"; then
log_success "规则 '$selected_rule' 已删除"
read -p "是否重启 Hysteria2 服务? [y/N]: " restart_service
if [[ $restart_service =~ ^[Yy]$ ]]; then
if systemctl restart hysteria-server 2>/dev/null; then
log_success "服务已重启"
else
log_warn "服务重启失败,请手动重启"
fi
if [[ "$rule_found" == true ]]; then
if mv "$temp_library" "$RULES_LIBRARY"; then
log_success "已从规则库中删除规则 '$rule_name'"
return 0
else
log_error "规则库更新失败"
rm -f "$temp_library"
return 1
fi
else
log_error "规则删除失败"
log_warn "在规则库中未找到规则 '$rule_name'"
rm -f "$temp_library"
return 1
fi
}
wait_for_user
# 从状态文件中移除规则的辅助函数
remove_rule_from_state() {
local rule_name="$1"
local temp_state="${RULES_STATE}.tmp"
if [[ -f "$RULES_STATE" ]]; then
awk -v rule="$rule_name" '
$0 == " - " rule { next }
{ print }
' "$RULES_STATE" > "$temp_state" 2>/dev/null
if [[ -s "$temp_state" ]]; then
mv "$temp_state" "$RULES_STATE" 2>/dev/null || rm -f "$temp_state"
else
rm -f "$temp_state"
fi
fi
}
# ===== 并发安全和临时文件管理函数 =====