mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-14 20:48:17 +08:00
💋 Sync 2026-11-02 01:02:56
This commit is contained in:
1 parent
d97dc35e3d
commit
2f941f0ffb
48 files changed
+4891
-1002
No files matched your search
+835
@@ -0,0 +1,835 @@
|
||||
#!/bin/sh
|
||||
|
||||
CONF="cf-optimizer"
|
||||
RUN_DIR="/tmp/run/cf-optimizer"
|
||||
TMP_DIR="/tmp/cf-optimizer"
|
||||
HOSTS_FILE="/tmp/hosts/cf-optimizer"
|
||||
LOG_FILE="/var/log/cf-optimizer.log"
|
||||
DEFAULT_IPS_FILE="/usr/share/cf-optimizer/default_ips.txt"
|
||||
COLO_MAP_FILE="/usr/share/cf-optimizer/colo_map.json"
|
||||
PID_FILE="$RUN_DIR/cf-optimizer.pid"
|
||||
COLO_CACHE_FILE="$RUN_DIR/colo_cache.txt"
|
||||
STATUS_JSON="$RUN_DIR/status.json"
|
||||
CURRENT_IP_FILE="$RUN_DIR/current_ip.txt"
|
||||
PRIMARY_IP_FILE="$RUN_DIR/primary_ip.txt"
|
||||
STANDBY_IP_FILE="$RUN_DIR/standby_ip.txt"
|
||||
BASELINE_TCP_FILE="$RUN_DIR/baseline_tcp.txt"
|
||||
FAIL_STREAK_FILE="$RUN_DIR/fail_streak.txt"
|
||||
|
||||
log() {
|
||||
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||
echo "$msg"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
echo "$msg" >> "$LOG_FILE"
|
||||
# 限制日志大小不超过 1000 行
|
||||
if [ -f "$LOG_FILE" ] && [ $(wc -l < "$LOG_FILE") -gt 1500 ]; then
|
||||
tail -n 800 "$LOG_FILE" > "$LOG_FILE.tmp" && mv -f "$LOG_FILE.tmp" "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
get_colo_name() {
|
||||
local code="$1"
|
||||
if [ -f "$COLO_MAP_FILE" ] && [ -n "$code" ]; then
|
||||
local name
|
||||
name=$(grep -E "\"$code\"[[:space:]]*:" "$COLO_MAP_FILE" 2>/dev/null | head -n 1 | sed -E 's/.*:[[:space:]]*"([^"]+)".*/\1/')
|
||||
if [ -n "$name" ]; then
|
||||
echo "$name"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
echo "${code:-未知}"
|
||||
}
|
||||
|
||||
update_colo_cache() {
|
||||
local ip="$1"
|
||||
local colo="$2"
|
||||
[ -z "$ip" ] || [ -z "$colo" ] && return
|
||||
mkdir -p "$RUN_DIR"
|
||||
touch "$COLO_CACHE_FILE"
|
||||
grep -v "^$ip[[:space:]]" "$COLO_CACHE_FILE" > "$COLO_CACHE_FILE.tmp" 2>/dev/null
|
||||
echo "$ip $colo" >> "$COLO_CACHE_FILE.tmp"
|
||||
mv -f "$COLO_CACHE_FILE.tmp" "$COLO_CACHE_FILE"
|
||||
}
|
||||
|
||||
get_cached_colo() {
|
||||
local ip="$1"
|
||||
[ ! -f "$COLO_CACHE_FILE" ] && return
|
||||
grep "^$ip[[:space:]]" "$COLO_CACHE_FILE" 2>/dev/null | tail -n 1 | awk '{print $2}'
|
||||
}
|
||||
|
||||
clean_hosts() {
|
||||
mkdir -p "$RUN_DIR"
|
||||
if [ -f "$HOSTS_FILE" ]; then
|
||||
rm -f "$HOSTS_FILE"
|
||||
killall -HUP dnsmasq 2>/dev/null
|
||||
log "已清空优选 hosts 映射并重载 dnsmasq"
|
||||
fi
|
||||
rm -f "$CURRENT_IP_FILE" "$PRIMARY_IP_FILE" "$STANDBY_IP_FILE" "$BASELINE_TCP_FILE" "$FAIL_STREAK_FILE"
|
||||
rm -rf "$RUN_DIR/task.lock"
|
||||
cat <<EOF > "$STATUS_JSON"
|
||||
{
|
||||
"running": false,
|
||||
"last_update": "",
|
||||
"best_ip": "",
|
||||
"best_colo": "",
|
||||
"best_tcp_lat": 0,
|
||||
"best_tls_lat": 0,
|
||||
"standby_ip": "",
|
||||
"standby_colo": "",
|
||||
"standby_tcp_lat": 0,
|
||||
"watchdog_status": "未运行",
|
||||
"domains": [],
|
||||
"rankings": []
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
sync_hosts() {
|
||||
. /lib/functions.sh
|
||||
config_load "$CONF"
|
||||
local enabled
|
||||
config_get_bool enabled settings enabled 0
|
||||
if [ "$enabled" != "1" ]; then
|
||||
clean_hosts
|
||||
return 0
|
||||
fi
|
||||
|
||||
local domains=""
|
||||
append_sync_domain() {
|
||||
local d="$1"
|
||||
[ -n "$d" ] && domains="$domains $d"
|
||||
}
|
||||
config_list_foreach settings domains append_sync_domain
|
||||
|
||||
if [ -z "$domains" ]; then
|
||||
clean_hosts
|
||||
return 0
|
||||
fi
|
||||
|
||||
local current_ip=""
|
||||
[ -f "$CURRENT_IP_FILE" ] && current_ip=$(cat "$CURRENT_IP_FILE" 2>/dev/null)
|
||||
|
||||
if [ -z "$current_ip" ]; then
|
||||
local cand_tmp="$TMP_DIR/cand_init.txt"
|
||||
mkdir -p "$TMP_DIR"
|
||||
load_candidate_ips "$cand_tmp"
|
||||
current_ip=$(head -n 1 "$cand_tmp" 2>/dev/null)
|
||||
rm -f "$cand_tmp"
|
||||
fi
|
||||
|
||||
[ -z "$current_ip" ] && return 0
|
||||
|
||||
local hosts_tmp="$HOSTS_FILE.tmp"
|
||||
mkdir -p "$(dirname "$HOSTS_FILE")"
|
||||
> "$hosts_tmp"
|
||||
echo "# Auto-generated by cf-optimizer sync on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
|
||||
for d in $domains; do
|
||||
echo "$current_ip $d" >> "$hosts_tmp"
|
||||
done
|
||||
mv -f "$hosts_tmp" "$HOSTS_FILE"
|
||||
killall -HUP dnsmasq 2>/dev/null
|
||||
log "已即时同步域名配置至 hosts 并重载 dnsmasq (定向至: $current_ip)"
|
||||
}
|
||||
|
||||
load_candidate_ips() {
|
||||
local out_file="$1"
|
||||
> "$out_file"
|
||||
|
||||
local ip_source
|
||||
config_get ip_source settings ip_source "builtin"
|
||||
|
||||
if [ "$ip_source" = "builtin" ] || [ "$ip_source" = "mixed" ]; then
|
||||
if [ -f "$DEFAULT_IPS_FILE" ]; then
|
||||
grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' "$DEFAULT_IPS_FILE" >> "$out_file"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$ip_source" = "custom" ] || [ "$ip_source" = "mixed" ]; then
|
||||
append_custom_ip() {
|
||||
local item="$1"
|
||||
[ -z "$item" ] && return
|
||||
# 若为 CIDR 简单网段 (如 /24),取前 3 个 IP
|
||||
case "$item" in
|
||||
*/*)
|
||||
local base_prefix=$(echo "$item" | cut -d'/' -f1 | cut -d'.' -f1-3)
|
||||
for host in 1 2 3; do
|
||||
echo "$base_prefix.$host" >> "$out_file"
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "$item" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' >> "$out_file"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
config_list_foreach settings custom_ips append_custom_ip
|
||||
fi
|
||||
|
||||
# 兜底:若文件为空,填入稳定官方公共 IP
|
||||
if [ ! -s "$out_file" ]; then
|
||||
echo "104.16.0.1" >> "$out_file"
|
||||
echo "104.17.0.1" >> "$out_file"
|
||||
echo "104.18.0.1" >> "$out_file"
|
||||
echo "172.64.0.1" >> "$out_file"
|
||||
fi
|
||||
|
||||
# 排序去重
|
||||
sort -u "$out_file" > "$out_file.tmp" && mv -f "$out_file.tmp" "$out_file"
|
||||
}
|
||||
|
||||
# 粗筛单节点探测
|
||||
probe_fast() {
|
||||
local ip="$1"
|
||||
local res_dir="$2"
|
||||
# 仅测 TCP 握手
|
||||
local connect_time
|
||||
connect_time=$(curl -s -k -o /dev/null --connect-timeout 1 -m 2 -w "%{time_connect}\n" \
|
||||
--resolve "speed.cloudflare.com:443:$ip" "https://speed.cloudflare.com/__down?bytes=0" 2>/dev/null)
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$connect_time" ] && [ "$connect_time" != "0.000000" ]; then
|
||||
local ms=$(awk "BEGIN {printf \"%08.2f\", $connect_time * 1000}")
|
||||
echo "$ms $ip" > "$res_dir/$ip"
|
||||
fi
|
||||
}
|
||||
|
||||
run_optimization() {
|
||||
. /lib/functions.sh
|
||||
config_load "$CONF"
|
||||
|
||||
local enabled
|
||||
config_get_bool enabled settings enabled 0
|
||||
if [ "$enabled" != "1" ]; then
|
||||
log "服务未启用,退出任务"
|
||||
clean_hosts
|
||||
return 0
|
||||
fi
|
||||
|
||||
local domains=""
|
||||
append_domain() {
|
||||
local d="$1"
|
||||
[ -n "$d" ] && domains="$domains $d"
|
||||
}
|
||||
config_list_foreach settings domains append_domain
|
||||
|
||||
if [ -z "$domains" ]; then
|
||||
log "未配置目标加速域名,跳过优选"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local concurrency sample_count hysteresis_ms
|
||||
config_get concurrency settings concurrency 10
|
||||
config_get sample_count settings sample_count 4
|
||||
config_get hysteresis_ms settings hysteresis_ms 30
|
||||
|
||||
local colo_filter_mode
|
||||
config_get colo_filter_mode settings colo_filter_mode "none"
|
||||
|
||||
local target_colos=""
|
||||
append_target_colo() {
|
||||
local item="$1"
|
||||
[ -z "$item" ] && return
|
||||
for c in $item; do
|
||||
local clean_c=$(echo "$c" | tr 'a-z' 'A-Z' | tr -d ' \t\r\n')
|
||||
if [ -n "$clean_c" ]; then
|
||||
case " $target_colos " in
|
||||
*" $clean_c "*) ;;
|
||||
*) target_colos="$target_colos $clean_c" ;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
if [ "$colo_filter_mode" != "none" ]; then
|
||||
local multi_colos=""
|
||||
config_get multi_colos settings colo_list ""
|
||||
[ -n "$multi_colos" ] && append_target_colo "$multi_colos"
|
||||
config_list_foreach settings colo_list append_target_colo
|
||||
config_list_foreach settings custom_colos append_target_colo
|
||||
fi
|
||||
|
||||
check_colo_allowed() {
|
||||
local colo="$1"
|
||||
[ -z "$colo" ] && return 0
|
||||
[ "$colo_filter_mode" = "none" ] && return 0
|
||||
|
||||
local match=0
|
||||
for c in $target_colos; do
|
||||
if [ "$c" = "$colo" ]; then
|
||||
match=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$colo_filter_mode" = "exclude" ]; then
|
||||
[ "$match" -eq 1 ] && return 1 || return 0
|
||||
elif [ "$colo_filter_mode" = "include" ]; then
|
||||
[ "$match" -eq 1 ] && return 0 || return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
log "开始执行优选任务,加速域名数: $(echo $domains | wc -w),机房策略: $colo_filter_mode${target_colos:+ ($target_colos)}"
|
||||
|
||||
local lock_dir="$RUN_DIR/task.lock"
|
||||
if ! mkdir "$lock_dir" 2>/dev/null; then
|
||||
local lock_pid=""
|
||||
[ -f "$lock_dir/pid" ] && lock_pid=$(cat "$lock_dir/pid" 2>/dev/null)
|
||||
if [ -n "$lock_pid" ] && ! kill -0 "$lock_pid" 2>/dev/null; then
|
||||
log "检测到残留任务锁 (PID: $lock_pid 已退出),自动释放死锁"
|
||||
rm -rf "$lock_dir"
|
||||
mkdir "$lock_dir" 2>/dev/null || return 0
|
||||
else
|
||||
log "已有优选任务正在运行中,跳过本次执行"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
echo $$ > "$lock_dir/pid"
|
||||
|
||||
mkdir -p "$TMP_DIR/fast" "$TMP_DIR/deep" "$RUN_DIR"
|
||||
rm -rf "$TMP_DIR/fast"/* "$TMP_DIR/deep"/*
|
||||
|
||||
local cand_file="$TMP_DIR/candidates.txt"
|
||||
load_candidate_ips "$cand_file"
|
||||
local total_cands=$(wc -l < "$cand_file")
|
||||
log "阶段一:开始并发初筛,候选节点总数: $total_cands,并发度: $concurrency"
|
||||
|
||||
# 并发初筛(采用 POSIX 批次等待机制,确保在任何非交互式 Shell 环境下严格限流)
|
||||
local running=0
|
||||
while read -r ip; do
|
||||
[ -z "$ip" ] && continue
|
||||
probe_fast "$ip" "$TMP_DIR/fast" &
|
||||
running=$((running + 1))
|
||||
if [ "$running" -ge "$concurrency" ]; then
|
||||
wait
|
||||
running=0
|
||||
fi
|
||||
done < "$cand_file"
|
||||
wait
|
||||
|
||||
# 汇总初筛候选,结合机房缓存预先剔除黑名单机房
|
||||
local fast_sorted_file="$TMP_DIR/fast_sorted.txt"
|
||||
cat "$TMP_DIR/fast"/* 2>/dev/null | sort -n | head -n 30 > "$fast_sorted_file"
|
||||
|
||||
local top_selected_file="$TMP_DIR/top_selected_ips.txt"
|
||||
> "$top_selected_file"
|
||||
local picked_count=0
|
||||
while read -r _lat ip; do
|
||||
[ -z "$ip" ] && continue
|
||||
local cached_colo=$(get_cached_colo "$ip")
|
||||
if [ -n "$cached_colo" ] && ! check_colo_allowed "$cached_colo"; then
|
||||
log "初筛预过滤: 节点 $ip ($cached_colo) 命中机房排除策略,自动略过"
|
||||
continue
|
||||
fi
|
||||
echo "$ip" >> "$top_selected_file"
|
||||
picked_count=$((picked_count + 1))
|
||||
[ "$picked_count" -ge 10 ] && break
|
||||
done < "$fast_sorted_file"
|
||||
|
||||
local top_count=$(wc -l < "$top_selected_file")
|
||||
if [ "$top_count" -eq 0 ]; then
|
||||
log "阶段一初筛无可用候选节点(或全部被机房区域策略过滤),请检查配置"
|
||||
rm -rf "$TMP_DIR"
|
||||
rm -rf "$lock_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local probe_domain=$(echo "$domains" | awk '{print $1}')
|
||||
[ -z "$probe_domain" ] && probe_domain="speed.cloudflare.com"
|
||||
|
||||
log "阶段一初筛完成,选出 $top_count 个候选节点进入精测采样,探活域名: $probe_domain(每节点轮询交叉采样 $sample_count 轮)"
|
||||
|
||||
# 阶段二:轮询交叉时序采样(跨越完整测试时段,精准捕获阶段性丢包与网络毛刺)
|
||||
mkdir -p "$TMP_DIR/samples"
|
||||
|
||||
for seq in $(seq 1 "$sample_count"); do
|
||||
while read -r ip; do
|
||||
[ -z "$ip" ] && continue
|
||||
[ -f "$TMP_DIR/blocked_${ip}" ] && continue
|
||||
[ -f "$TMP_DIR/colo_excluded_${ip}" ] && continue
|
||||
|
||||
local raw_header="$TMP_DIR/header_${ip}_${seq}.txt"
|
||||
local metrics
|
||||
metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
|
||||
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
|
||||
--resolve "$probe_domain:443:$ip" "https://$probe_domain/" \
|
||||
-D "$raw_header" \
|
||||
-w "%{time_connect} %{time_appconnect} %{http_code}\n" 2>/dev/null)
|
||||
|
||||
local tcp_sec=$(echo "$metrics" | awk '{print $1}')
|
||||
local tls_sec=$(echo "$metrics" | awk '{print $2}')
|
||||
local http_code=$(echo "$metrics" | awk '{print $3}')
|
||||
|
||||
if [ "$http_code" = "403" ]; then
|
||||
touch "$TMP_DIR/blocked_${ip}"
|
||||
rm -f "$raw_header"
|
||||
continue
|
||||
fi
|
||||
|
||||
local last_colo=""
|
||||
if [ -f "$raw_header" ]; then
|
||||
local cf_ray=$(grep -i '^cf-ray:' "$raw_header" 2>/dev/null | tr -d '\r\n')
|
||||
if [ -n "$cf_ray" ]; then
|
||||
last_colo=$(echo "$cf_ray" | sed -E 's/.*-([A-Za-z0-9]+).*/\1/' | tr 'a-z' 'A-Z')
|
||||
fi
|
||||
rm -f "$raw_header"
|
||||
fi
|
||||
|
||||
if [ -n "$last_colo" ]; then
|
||||
update_colo_cache "$ip" "$last_colo"
|
||||
if ! check_colo_allowed "$last_colo"; then
|
||||
log "节点 $ip 机房 ($last_colo) 不符合区域过滤策略 ($colo_filter_mode),已终止采样并排除"
|
||||
touch "$TMP_DIR/colo_excluded_${ip}"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$http_code" ] && [ "$http_code" != "000" ] && [ -n "$tcp_sec" ] && [ "$tcp_sec" != "0.000000" ]; then
|
||||
local tcp_ms=$(awk "BEGIN {printf \"%.2f\", $tcp_sec * 1000}")
|
||||
local tls_ms=$(awk "BEGIN {printf \"%.2f\", $tls_sec * 1000}")
|
||||
echo "1|$tcp_ms|$tls_ms|$last_colo" >> "$TMP_DIR/samples/$ip"
|
||||
else
|
||||
echo "0|0|0|$last_colo" >> "$TMP_DIR/samples/$ip"
|
||||
fi
|
||||
done < "$top_selected_file"
|
||||
done
|
||||
|
||||
# 汇总统计各节点采样指标
|
||||
while read -r ip; do
|
||||
[ -z "$ip" ] && continue
|
||||
if [ -f "$TMP_DIR/blocked_${ip}" ]; then
|
||||
log "节点 $ip 触发权限阻断 (HTTP 403/企业限制),已自动排除"
|
||||
continue
|
||||
fi
|
||||
if [ -f "$TMP_DIR/colo_excluded_${ip}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
local sample_file="$TMP_DIR/samples/$ip"
|
||||
[ ! -f "$sample_file" ] && continue
|
||||
|
||||
local success_count=0
|
||||
local total_tcp=0
|
||||
local total_tls=0
|
||||
local min_tcp=99999
|
||||
local max_tcp=0
|
||||
local last_colo=""
|
||||
|
||||
while IFS='|' read -r ok tcp_ms tls_ms colo; do
|
||||
[ -n "$colo" ] && last_colo="$colo"
|
||||
if [ "$ok" = "1" ]; then
|
||||
success_count=$((success_count + 1))
|
||||
total_tcp=$(awk "BEGIN {printf \"%.2f\", $total_tcp + $tcp_ms}")
|
||||
total_tls=$(awk "BEGIN {printf \"%.2f\", $total_tls + $tls_ms}")
|
||||
min_tcp=$(awk "BEGIN {if ($tcp_ms < $min_tcp) print $tcp_ms; else print $min_tcp}")
|
||||
max_tcp=$(awk "BEGIN {if ($tcp_ms > $max_tcp) print $tcp_ms; else print $max_tcp}")
|
||||
fi
|
||||
done < "$sample_file"
|
||||
|
||||
if [ -z "$last_colo" ]; then
|
||||
last_colo=$(get_cached_colo "$ip")
|
||||
fi
|
||||
|
||||
if ! check_colo_allowed "$last_colo"; then
|
||||
log "节点 $ip 机房 ($last_colo) 最终区域校验未通过,舍弃"
|
||||
continue
|
||||
fi
|
||||
|
||||
local loss_rate=$(awk "BEGIN {printf \"%.1f\", (1 - $success_count / $sample_count) * 100}")
|
||||
if [ "$success_count" -gt 0 ]; then
|
||||
local avg_tcp=$(awk "BEGIN {printf \"%.2f\", $total_tcp / $success_count}")
|
||||
local avg_tls=$(awk "BEGIN {printf \"%.2f\", $total_tls / $success_count}")
|
||||
local jitter=$(awk "BEGIN {printf \"%.2f\", $max_tcp - $min_tcp}")
|
||||
# 综合评分:平均时延 + 抖动*0.5 + 丢包率*10(采用固定宽度 0 填充)
|
||||
local score=$(awk "BEGIN {printf \"%08.2f\", $avg_tcp + $jitter * 0.5 + $loss_rate * 10}")
|
||||
|
||||
local colo_disp=$(get_colo_name "$last_colo")
|
||||
echo "$score|$ip|$avg_tcp|$min_tcp|$max_tcp|$avg_tls|$loss_rate%|$colo_disp|$last_colo" >> "$TMP_DIR/deep_results.txt"
|
||||
fi
|
||||
done < "$top_selected_file"
|
||||
|
||||
if [ ! -s "$TMP_DIR/deep_results.txt" ]; then
|
||||
log "精测采样后无可用健康节点"
|
||||
rm -rf "$TMP_DIR"
|
||||
rm -rf "$lock_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 排序:按 score 从低到高数值排序
|
||||
sort -n "$TMP_DIR/deep_results.txt" > "$TMP_DIR/deep_sorted.txt"
|
||||
|
||||
local best_line=$(head -n 1 "$TMP_DIR/deep_sorted.txt")
|
||||
local best_score=$(echo "$best_line" | cut -d'|' -f1)
|
||||
local best_ip=$(echo "$best_line" | cut -d'|' -f2)
|
||||
local best_avg_tcp=$(echo "$best_line" | cut -d'|' -f3)
|
||||
local best_min_tcp=$(echo "$best_line" | cut -d'|' -f4)
|
||||
local best_max_tcp=$(echo "$best_line" | cut -d'|' -f5)
|
||||
local best_avg_tls=$(echo "$best_line" | cut -d'|' -f6)
|
||||
local best_loss=$(echo "$best_line" | cut -d'|' -f7)
|
||||
local best_colo=$(echo "$best_line" | cut -d'|' -f8)
|
||||
|
||||
log "测速完成,最优候选: $best_ip ($best_colo), 平均延迟: ${best_avg_tcp}ms, 丢包率: $best_loss"
|
||||
|
||||
# 迟滞逻辑判断
|
||||
local current_ip=""
|
||||
[ -f "$CURRENT_IP_FILE" ] && current_ip=$(cat "$CURRENT_IP_FILE")
|
||||
|
||||
local final_ip="$best_ip"
|
||||
local final_colo="$best_colo"
|
||||
local final_tcp="$best_avg_tcp"
|
||||
local final_tls="$best_avg_tls"
|
||||
|
||||
if [ -n "$current_ip" ] && [ "$current_ip" != "$best_ip" ]; then
|
||||
local cur_info=$(grep "|$current_ip|" "$TMP_DIR/deep_sorted.txt" 2>/dev/null)
|
||||
local cur_colo_allowed=1
|
||||
local cur_cached_colo=$(get_cached_colo "$current_ip")
|
||||
if [ -n "$cur_cached_colo" ] && ! check_colo_allowed "$cur_cached_colo"; then
|
||||
cur_colo_allowed=0
|
||||
fi
|
||||
|
||||
if [ -n "$cur_info" ] && [ "$cur_colo_allowed" -eq 1 ]; then
|
||||
local cur_tcp=$(echo "$cur_info" | cut -d'|' -f3)
|
||||
local cur_loss=$(echo "$cur_info" | cut -d'|' -f7)
|
||||
local cur_colo=$(echo "$cur_info" | cut -d'|' -f8)
|
||||
|
||||
if [ "$cur_loss" = "0.0%" ]; then
|
||||
# 测算相比当前在用 IP 的绝对时延差 (ms)
|
||||
local diff_ms=$(awk "BEGIN {if ($cur_tcp > $best_avg_tcp) printf \"%.2f\", $cur_tcp - $best_avg_tcp; else print 0}")
|
||||
local is_better=$(awk "BEGIN {print ($diff_ms >= $hysteresis_ms) ? 1 : 0}")
|
||||
|
||||
if [ "$is_better" -eq 0 ]; then
|
||||
log "新优选 IP 优化幅度 (${diff_ms}ms) 未达迟滞阈值 (${hysteresis_ms}ms),保持当前在用 IP: $current_ip"
|
||||
final_ip="$current_ip"
|
||||
final_colo="$cur_colo"
|
||||
final_tcp="$cur_tcp"
|
||||
final_tls=$(echo "$cur_info" | cut -d'|' -f6)
|
||||
else
|
||||
log "新优选 IP 性能显著提升 (时延改善 ${diff_ms}ms >= ${hysteresis_ms}ms),触发切换"
|
||||
fi
|
||||
else
|
||||
log "当前在用 IP 发生丢包 ($cur_loss),强制切换至新最优 IP: $best_ip"
|
||||
fi
|
||||
else
|
||||
if [ "$cur_colo_allowed" -eq 0 ]; then
|
||||
log "当前在用 IP ($current_ip / $cur_cached_colo) 已被机房过滤策略排除,强制切换至新最优 IP: $best_ip"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# 原子写入 /tmp/hosts/cf-optimizer
|
||||
local hosts_tmp="$HOSTS_FILE.tmp"
|
||||
mkdir -p "$(dirname "$HOSTS_FILE")"
|
||||
> "$hosts_tmp"
|
||||
echo "# Auto-generated by cf-optimizer on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
|
||||
for d in $domains; do
|
||||
echo "$final_ip $d" >> "$hosts_tmp"
|
||||
done
|
||||
mv -f "$hosts_tmp" "$HOSTS_FILE"
|
||||
echo "$final_ip" > "$CURRENT_IP_FILE"
|
||||
echo "$final_ip" > "$PRIMARY_IP_FILE"
|
||||
echo "0" > "$FAIL_STREAK_FILE"
|
||||
|
||||
# 选拔次优备选节点(排名紧随其后且 IP 不同的健康节点)
|
||||
local standby_ip=""
|
||||
local standby_colo="无备选"
|
||||
local standby_tcp="-"
|
||||
while IFS='|' read -r _sc _ip _atip _minip _maxip _attls _lrate _cdisp _craw; do
|
||||
if [ -n "$_ip" ] && [ "$_ip" != "$final_ip" ]; then
|
||||
standby_ip="$_ip"
|
||||
standby_colo="$_cdisp"
|
||||
standby_tcp="$_atip"
|
||||
break
|
||||
fi
|
||||
done < "$TMP_DIR/deep_sorted.txt"
|
||||
echo "$standby_ip|$standby_colo|$standby_tcp" > "$STANDBY_IP_FILE"
|
||||
echo "$final_tcp" > "$BASELINE_TCP_FILE"
|
||||
|
||||
# 发送 HUP 信号通知 dnsmasq 毫秒级重载
|
||||
killall -HUP dnsmasq 2>/dev/null
|
||||
log "已更新 $HOSTS_FILE 并热重载 dnsmasq,在用主选: $final_ip ($final_colo),次优备选: ${standby_ip:-无} ($standby_colo)"
|
||||
|
||||
# 组装状态 JSON 给 LuCI 前端消费
|
||||
local now_str=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local domain_json="["
|
||||
local first_d=1
|
||||
for d in $domains; do
|
||||
[ "$first_d" -eq 0 ] && domain_json="$domain_json,"
|
||||
domain_json="$domain_json{\"domain\":\"$d\",\"ip\":\"$final_ip\",\"colo\":\"$final_colo\",\"tcp_lat\":\"$final_tcp ms\",\"tls_lat\":\"$final_tls ms\",\"status\":\"正常\"}"
|
||||
first_d=0
|
||||
done
|
||||
domain_json="$domain_json]"
|
||||
|
||||
local rankings_json="["
|
||||
local first_r=1
|
||||
local rank_idx=1
|
||||
while IFS='|' read -r sc ip atip minip maxip attls lrate cdisp craw; do
|
||||
[ -z "$ip" ] && continue
|
||||
[ "$first_r" -eq 0 ] && rankings_json="$rankings_json,"
|
||||
rankings_json="$rankings_json{\"rank\":$rank_idx,\"ip\":\"$ip\",\"colo\":\"$cdisp\",\"avg_lat\":\"$atip ms\",\"lat_range\":\"$minip / $maxip ms\",\"loss_rate\":\"$lrate\"}"
|
||||
first_r=0
|
||||
rank_idx=$((rank_idx + 1))
|
||||
[ "$rank_idx" -gt 5 ] && break
|
||||
done < "$TMP_DIR/deep_sorted.txt"
|
||||
rankings_json="$rankings_json]"
|
||||
|
||||
cat <<EOF > "$STATUS_JSON.tmp"
|
||||
{
|
||||
"running": true,
|
||||
"last_update": "$now_str",
|
||||
"best_ip": "$final_ip",
|
||||
"best_colo": "$final_colo",
|
||||
"best_tcp_lat": "$final_tcp ms",
|
||||
"best_tls_lat": "$final_tls ms",
|
||||
"standby_ip": "$standby_ip",
|
||||
"standby_colo": "$standby_colo",
|
||||
"standby_tcp_lat": "$standby_tcp ms",
|
||||
"watchdog_status": "正常监控中",
|
||||
"domains": $domain_json,
|
||||
"rankings": $rankings_json
|
||||
}
|
||||
EOF
|
||||
mv -f "$STATUS_JSON.tmp" "$STATUS_JSON"
|
||||
rm -rf "$TMP_DIR"
|
||||
rm -rf "$lock_dir"
|
||||
return 0
|
||||
}
|
||||
|
||||
apply_fast_failover() {
|
||||
local target_ip="$1"
|
||||
local target_colo="$2"
|
||||
local target_tcp="${3:--}"
|
||||
|
||||
. /lib/functions.sh
|
||||
config_load "$CONF"
|
||||
local domains=""
|
||||
append_failover_domain() {
|
||||
local d="$1"
|
||||
[ -n "$d" ] && domains="$domains $d"
|
||||
}
|
||||
config_list_foreach settings domains append_failover_domain
|
||||
|
||||
local hosts_tmp="$HOSTS_FILE.tmp"
|
||||
mkdir -p "$(dirname "$HOSTS_FILE")"
|
||||
> "$hosts_tmp"
|
||||
echo "# Auto-generated by cf-optimizer failover on $(date '+%Y-%m-%d %H:%M:%S')" >> "$hosts_tmp"
|
||||
for d in $domains; do
|
||||
echo "$target_ip $d" >> "$hosts_tmp"
|
||||
done
|
||||
mv -f "$hosts_tmp" "$HOSTS_FILE"
|
||||
echo "$target_ip" > "$CURRENT_IP_FILE"
|
||||
echo "" > "$STANDBY_IP_FILE"
|
||||
if [ -n "$target_tcp" ] && [ "$target_tcp" != "-" ]; then
|
||||
echo "$target_tcp" > "$BASELINE_TCP_FILE"
|
||||
else
|
||||
rm -f "$BASELINE_TCP_FILE"
|
||||
fi
|
||||
|
||||
killall -HUP dnsmasq 2>/dev/null
|
||||
log "已将 hosts 紧急倒换至次优节点: $target_ip ($target_colo) 并重载 dnsmasq"
|
||||
|
||||
if [ -f "$STATUS_JSON" ]; then
|
||||
local now_str=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
local domain_json="["
|
||||
local first_d=1
|
||||
for d in $domains; do
|
||||
[ "$first_d" -eq 0 ] && domain_json="$domain_json,"
|
||||
domain_json="$domain_json{\"domain\":\"$d\",\"ip\":\"$target_ip\",\"colo\":\"$target_colo\",\"tcp_lat\":\"$target_tcp ms\",\"tls_lat\":\"-\",\"status\":\"已倒换\"}"
|
||||
first_d=0
|
||||
done
|
||||
domain_json="$domain_json]"
|
||||
|
||||
local tmp_status="$STATUS_JSON.tmp"
|
||||
sed -E "s/\"best_ip\":[[:space:]]*\"[^\"]*\"/\"best_ip\": \"$target_ip\"/" "$STATUS_JSON" | \
|
||||
sed -E "s/\"best_colo\":[[:space:]]*\"[^\"]*\"/\"best_colo\": \"$target_colo\"/" | \
|
||||
sed -E "s/\"standby_ip\":[[:space:]]*\"[^\"]*\"/\"standby_ip\": \"\"/" | \
|
||||
sed -E "s/\"standby_colo\":[[:space:]]*\"[^\"]*\"/\"standby_colo\": \"已倒换转正\"/" | \
|
||||
sed -E "s/\"last_update\":[[:space:]]*\"[^\"]*\"/\"last_update\": \"$now_str (已触发倒换)\"/" | \
|
||||
sed -E "s/\"watchdog_status\":[[:space:]]*\"[^\"]*\"/\"watchdog_status\": \"已触发故障倒换\"/" | \
|
||||
sed -E "s/\"domains\":[[:space:]]*\[[^]]*\]/\"domains\": $domain_json/" > "$tmp_status" 2>/dev/null
|
||||
[ -s "$tmp_status" ] && mv -f "$tmp_status" "$STATUS_JSON"
|
||||
fi
|
||||
}
|
||||
|
||||
check_health_and_failover() {
|
||||
[ ! -f "$CURRENT_IP_FILE" ] && return 0
|
||||
local current_ip=$(cat "$CURRENT_IP_FILE" 2>/dev/null)
|
||||
[ -z "$current_ip" ] && return 0
|
||||
|
||||
. /lib/functions.sh
|
||||
config_load "$CONF"
|
||||
|
||||
local failover_enabled fail_threshold latency_multiplier
|
||||
config_get_bool failover_enabled settings failover_enabled 1
|
||||
[ "$failover_enabled" != "1" ] && return 0
|
||||
config_get fail_threshold settings fail_threshold 2
|
||||
config_get latency_multiplier settings latency_multiplier "2.0"
|
||||
|
||||
local domains=""
|
||||
append_check_domain() {
|
||||
local d="$1"
|
||||
[ -n "$d" ] && domains="$domains $d"
|
||||
}
|
||||
config_list_foreach settings domains append_check_domain
|
||||
local probe_domain=$(echo "$domains" | awk '{print $1}')
|
||||
[ -z "$probe_domain" ] && probe_domain="speed.cloudflare.com"
|
||||
|
||||
local metrics
|
||||
metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
|
||||
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
|
||||
--resolve "$probe_domain:443:$current_ip" "https://$probe_domain/" \
|
||||
-w "%{time_connect} %{http_code}\n" 2>/dev/null)
|
||||
|
||||
local tcp_sec=$(echo "$metrics" | awk '{print $1}')
|
||||
local http_code=$(echo "$metrics" | awk '{print $2}')
|
||||
local cur_tcp_ms=0
|
||||
if [ -n "$tcp_sec" ] && [ "$tcp_sec" != "0.000000" ]; then
|
||||
cur_tcp_ms=$(awk "BEGIN {printf \"%.2f\", $tcp_sec * 1000}")
|
||||
fi
|
||||
|
||||
local baseline_tcp=0
|
||||
[ -f "$BASELINE_TCP_FILE" ] && baseline_tcp=$(cat "$BASELINE_TCP_FILE" 2>/dev/null)
|
||||
baseline_tcp=${baseline_tcp:-0}
|
||||
|
||||
local streak=0
|
||||
[ -f "$FAIL_STREAK_FILE" ] && streak=$(cat "$FAIL_STREAK_FILE" 2>/dev/null)
|
||||
streak=${streak:-0}
|
||||
|
||||
local is_abnormal=0
|
||||
local abnormal_reason=""
|
||||
|
||||
if [ -z "$http_code" ] || [ "$http_code" = "000" ] || [ "$http_code" = "403" ]; then
|
||||
is_abnormal=1
|
||||
abnormal_reason="HTTP ${http_code:-000} 阻断或超时"
|
||||
elif [ "$latency_multiplier" != "0" ] && [ "$latency_multiplier" != "0.0" ]; then
|
||||
local is_degraded
|
||||
is_degraded=$(awk "BEGIN {
|
||||
if ($baseline_tcp > 0 && $cur_tcp_ms >= ($baseline_tcp * $latency_multiplier) && ($cur_tcp_ms - $baseline_tcp) >= 50)
|
||||
print 1;
|
||||
else
|
||||
print 0;
|
||||
}")
|
||||
if [ "$is_degraded" -eq 1 ]; then
|
||||
is_abnormal=1
|
||||
abnormal_reason="延迟严重劣化 (${cur_tcp_ms}ms >= 基线 ${baseline_tcp}ms x ${latency_multiplier})"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$is_abnormal" -eq 0 ]; then
|
||||
if [ "$streak" -gt 0 ]; then
|
||||
log "主用优选节点 $current_ip 探活恢复正常 (HTTP $http_code, TCP ${cur_tcp_ms}ms)"
|
||||
echo "0" > "$FAIL_STREAK_FILE"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
streak=$((streak + 1))
|
||||
echo "$streak" > "$FAIL_STREAK_FILE"
|
||||
log "在用优选节点 $current_ip 探活异常 [$abnormal_reason],连续异常: $streak/$fail_threshold"
|
||||
|
||||
if [ "$streak" -ge "$fail_threshold" ]; then
|
||||
echo "0" > "$FAIL_STREAK_FILE"
|
||||
local standby_info=""
|
||||
[ -f "$STANDBY_IP_FILE" ] && standby_info=$(cat "$STANDBY_IP_FILE" 2>/dev/null)
|
||||
local standby_ip=$(echo "$standby_info" | cut -d'|' -f1)
|
||||
local standby_colo=$(echo "$standby_info" | cut -d'|' -f2)
|
||||
|
||||
if [ -n "$standby_ip" ] && [ "$standby_ip" != "$current_ip" ]; then
|
||||
log "正在核验次优备选节点健康度: $standby_ip ($standby_colo)"
|
||||
local s_metrics
|
||||
s_metrics=$(curl -s -k -I -o /dev/null --connect-timeout 2 -m 3 \
|
||||
-A "Mozilla/5.0 (compatible; Cloudflare-Optimizer/1.0)" \
|
||||
--resolve "$probe_domain:443:$standby_ip" "https://$probe_domain/" \
|
||||
-w "%{time_connect} %{http_code}\n" 2>/dev/null)
|
||||
local s_tcp_sec=$(echo "$s_metrics" | awk '{print $1}')
|
||||
local s_code=$(echo "$s_metrics" | awk '{print $2}')
|
||||
local s_tcp_ms="-"
|
||||
[ -n "$s_tcp_sec" ] && [ "$s_tcp_sec" != "0.000000" ] && s_tcp_ms=$(awk "BEGIN {printf \"%.2f\", $s_tcp_sec * 1000}")
|
||||
|
||||
if [ -n "$s_code" ] && [ "$s_code" != "000" ] && [ "$s_code" != "403" ]; then
|
||||
log "主用节点异常,次优备选节点验证健康 (HTTP $s_code, TCP ${s_tcp_ms}ms),秒级倒换至次优节点"
|
||||
apply_fast_failover "$standby_ip" "$standby_colo" "$s_tcp_ms"
|
||||
return 0
|
||||
else
|
||||
log "次优备选节点同样不可用 (HTTP ${s_code:-000}),提前拉起全网节点紧急重测"
|
||||
fi
|
||||
else
|
||||
log "未配置次优备选节点,提前拉起全网节点紧急重测"
|
||||
fi
|
||||
|
||||
run_optimization
|
||||
fi
|
||||
}
|
||||
|
||||
daemon_loop() {
|
||||
echo $$ > "$PID_FILE"
|
||||
trap 'rm -f "$PID_FILE"; exit 0' INT TERM EXIT
|
||||
|
||||
log "守护进程启动 [PID: $$]"
|
||||
|
||||
local last_full_run=0
|
||||
|
||||
while true; do
|
||||
. /lib/functions.sh
|
||||
config_load "$CONF"
|
||||
|
||||
local enabled
|
||||
config_get_bool enabled settings enabled 0
|
||||
if [ "$enabled" != "1" ]; then
|
||||
log "配置已禁用,清理并退出守护进程"
|
||||
clean_hosts
|
||||
break
|
||||
fi
|
||||
|
||||
local interval failover_enabled health_interval
|
||||
config_get interval settings interval 120
|
||||
[ "$interval" -lt 10 ] && interval=10
|
||||
local full_interval_sec=$((interval * 60))
|
||||
|
||||
config_get_bool failover_enabled settings failover_enabled 1
|
||||
config_get health_interval settings health_interval 60
|
||||
[ "$health_interval" -lt 15 ] && health_interval=15
|
||||
|
||||
local now=$(date +%s)
|
||||
if [ "$last_full_run" -eq 0 ] || [ $((now - last_full_run)) -ge "$full_interval_sec" ]; then
|
||||
run_optimization
|
||||
last_full_run=$(date +%s)
|
||||
elif [ "$failover_enabled" = "1" ]; then
|
||||
check_health_and_failover
|
||||
fi
|
||||
|
||||
local sleep_sec="$health_interval"
|
||||
if [ "$failover_enabled" != "1" ]; then
|
||||
sleep_sec=30
|
||||
fi
|
||||
|
||||
local elapsed=0
|
||||
while [ "$elapsed" -lt "$sleep_sec" ]; do
|
||||
sleep 5 &
|
||||
wait $!
|
||||
elapsed=$((elapsed + 5))
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
run)
|
||||
run_optimization
|
||||
;;
|
||||
sync-hosts)
|
||||
sync_hosts
|
||||
;;
|
||||
daemon)
|
||||
daemon_loop
|
||||
;;
|
||||
clean)
|
||||
clean_hosts
|
||||
;;
|
||||
status)
|
||||
if [ -f "$STATUS_JSON" ]; then
|
||||
cat "$STATUS_JSON"
|
||||
else
|
||||
echo '{"running":false,"domains":[],"rankings":[]}'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 {run|sync-hosts|daemon|clean|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"HKG": "香港 HKG",
|
||||
"TPE": "台北 TPE",
|
||||
"KHH": "高雄 KHH",
|
||||
"NRT": "东京 NRT",
|
||||
"HND": "东京羽田 HND",
|
||||
"KIX": "大阪 KIX",
|
||||
"FUK": "福冈 FUK",
|
||||
"ICN": "首尔 ICN",
|
||||
"SIN": "新加坡 SIN",
|
||||
"BKK": "曼谷 BKK",
|
||||
"KUL": "吉隆坡 KUL",
|
||||
"JHB": "新山 JHB",
|
||||
"MNL": "马尼拉 MNL",
|
||||
"SGN": "胡志明市 SGN",
|
||||
"HAN": "河内 HAN",
|
||||
"CGK": "雅加达 CGK",
|
||||
"DPS": "巴厘岛 DPS",
|
||||
"SJC": "圣何塞 SJC",
|
||||
"SFO": "旧金山 SFO",
|
||||
"LAX": "洛杉矶 LAX",
|
||||
"SEA": "西雅图 SEA",
|
||||
"PDX": "波特兰 PDX",
|
||||
"DEN": "丹佛 DEN",
|
||||
"PHX": "菲尼克斯 PHX",
|
||||
"DFW": "达拉斯 DFW",
|
||||
"IAH": "休斯顿 IAH",
|
||||
"ORD": "芝加哥 ORD",
|
||||
"ATL": "亚特兰大 ATL",
|
||||
"MIA": "迈阿密 MIA",
|
||||
"IAD": "华盛顿 IAD",
|
||||
"EWR": "纽瓦克 EWR",
|
||||
"JFK": "纽约 JFK",
|
||||
"BOS": "波士顿 BOS",
|
||||
"YVR": "温哥华 YVR",
|
||||
"YYZ": "多伦多 YYZ",
|
||||
"YUL": "蒙特利尔 YUL",
|
||||
"FRA": "法兰克福 FRA",
|
||||
"LHR": "伦敦 LHR",
|
||||
"AMS": "阿姆斯特丹 AMS",
|
||||
"CDG": "巴黎 CDG",
|
||||
"MAD": "马德里 MAD",
|
||||
"MXP": "米兰 MXP",
|
||||
"ZRH": "苏黎世 ZRH",
|
||||
"VIE": "维也纳 VIE",
|
||||
"ARN": "斯德哥尔摩 ARN",
|
||||
"HEL": "赫尔辛基 HEL",
|
||||
"CPH": "哥本哈根 CPH",
|
||||
"WAW": "华沙 WAW",
|
||||
"PRG": "布拉格 PRG",
|
||||
"SYD": "悉尼 SYD",
|
||||
"MEL": "墨尔本 MEL",
|
||||
"BNE": "布里斯班 BNE",
|
||||
"PER": "珀斯 PER",
|
||||
"AKL": "奥克兰 AKL",
|
||||
"DXB": "迪拜 DXB",
|
||||
"DOH": "多哈 DOH"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
104.16.0.1
|
||||
104.16.1.1
|
||||
104.16.8.1
|
||||
104.16.16.1
|
||||
104.16.24.1
|
||||
104.16.32.1
|
||||
104.16.48.1
|
||||
104.16.64.1
|
||||
104.16.80.1
|
||||
104.16.96.1
|
||||
104.16.112.1
|
||||
104.16.128.1
|
||||
104.16.144.1
|
||||
104.16.160.1
|
||||
104.16.176.1
|
||||
104.16.192.1
|
||||
104.16.208.1
|
||||
104.16.224.1
|
||||
104.16.240.1
|
||||
104.17.0.1
|
||||
104.17.16.1
|
||||
104.17.32.1
|
||||
104.17.64.1
|
||||
104.17.96.1
|
||||
104.17.128.1
|
||||
104.17.160.1
|
||||
104.17.192.1
|
||||
104.17.224.1
|
||||
104.18.0.1
|
||||
104.18.16.1
|
||||
104.18.32.1
|
||||
104.18.64.1
|
||||
104.18.96.1
|
||||
104.18.128.1
|
||||
104.18.160.1
|
||||
104.18.192.1
|
||||
104.18.224.1
|
||||
104.19.0.1
|
||||
104.19.16.1
|
||||
104.19.32.1
|
||||
104.19.64.1
|
||||
104.19.96.1
|
||||
104.19.128.1
|
||||
104.19.160.1
|
||||
104.19.192.1
|
||||
104.19.224.1
|
||||
104.20.0.1
|
||||
104.20.32.1
|
||||
104.20.64.1
|
||||
104.20.96.1
|
||||
104.20.128.1
|
||||
104.20.160.1
|
||||
104.20.192.1
|
||||
104.21.0.1
|
||||
104.21.16.1
|
||||
104.21.32.1
|
||||
104.21.64.1
|
||||
104.21.96.1
|
||||
104.22.0.1
|
||||
104.22.32.1
|
||||
104.22.64.1
|
||||
104.24.0.1
|
||||
104.24.32.1
|
||||
104.25.0.1
|
||||
104.25.32.1
|
||||
104.26.0.1
|
||||
104.26.32.1
|
||||
104.27.0.1
|
||||
104.27.32.1
|
||||
172.64.0.1
|
||||
172.64.16.1
|
||||
172.64.32.1
|
||||
172.64.64.1
|
||||
172.64.96.1
|
||||
172.64.128.1
|
||||
172.64.160.1
|
||||
172.64.192.1
|
||||
172.65.0.1
|
||||
172.65.32.1
|
||||
172.66.0.1
|
||||
172.66.32.1
|
||||
172.67.0.1
|
||||
172.67.32.1
|
||||
172.67.64.1
|
||||
172.67.128.1
|
||||
162.158.0.1
|
||||
162.158.16.1
|
||||
162.158.32.1
|
||||
162.158.64.1
|
||||
162.158.128.1
|
||||
108.162.192.1
|
||||
108.162.208.1
|
||||
108.162.224.1
|
||||
141.101.64.1
|
||||
141.101.96.1
|
||||
141.101.120.1
|
||||
190.93.240.1
|
||||
197.234.240.1
|
||||
198.41.128.1
|
||||
198.41.160.1
|
||||
198.41.192.1
|
||||
198.41.224.1
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"admin/services/cf-optimizer": {
|
||||
"title": "Cloudflare 优选加速",
|
||||
"order": 65,
|
||||
"action": {
|
||||
"type": "firstchild"
|
||||
},
|
||||
"depends": {
|
||||
"acl": [ "luci-app-cf-optimizer" ],
|
||||
"uci": { "cf-optimizer": true }
|
||||
}
|
||||
},
|
||||
"admin/services/cf-optimizer/overview": {
|
||||
"title": "状态概览",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "cf-optimizer/overview"
|
||||
}
|
||||
},
|
||||
"admin/services/cf-optimizer/settings": {
|
||||
"title": "参数配置",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "cf-optimizer/settings"
|
||||
}
|
||||
},
|
||||
"admin/services/cf-optimizer/log": {
|
||||
"title": "运行日志",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "cf-optimizer/log"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"luci-app-cf-optimizer": {
|
||||
"description": "Grant access to cf-optimizer LuCI procedures",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.cf-optimizer": [
|
||||
"get_status",
|
||||
"get_logs"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"cf-optimizer"
|
||||
]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.cf-optimizer": [
|
||||
"run_once",
|
||||
"clear_logs",
|
||||
"service_action"
|
||||
]
|
||||
},
|
||||
"uci": [
|
||||
"cf-optimizer"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
import { connect } from 'ubus';
|
||||
import { access, readfile, writefile, stat, popen } from 'fs';
|
||||
|
||||
function exec(cmd) {
|
||||
let p = popen(cmd, 'r');
|
||||
if (!p) return { code: -1, stdout: [] };
|
||||
let out = [];
|
||||
while (true) {
|
||||
let line = p.read('line');
|
||||
if (line == null) break;
|
||||
push(out, rtrim(line, '\r\n'));
|
||||
}
|
||||
let code = p.close();
|
||||
return { code: code, stdout: out };
|
||||
}
|
||||
|
||||
const _trim = trim;
|
||||
function trim_str(s) {
|
||||
if (s == null) return '';
|
||||
return _trim('' + s);
|
||||
}
|
||||
|
||||
function get_procd_status() {
|
||||
let u = connect();
|
||||
if (!u) return { running: false };
|
||||
|
||||
let res = u.call('service', 'list', { name: 'cf-optimizer' });
|
||||
if (res && res['cf-optimizer'] && res['cf-optimizer'].instances) {
|
||||
let instances = res['cf-optimizer'].instances;
|
||||
for (let name in instances) {
|
||||
let inst = instances[name];
|
||||
if (inst && inst.running)
|
||||
return { running: true, pid: inst.pid };
|
||||
}
|
||||
}
|
||||
|
||||
let pid_res = exec('pidof cf-optimizer 2>/dev/null');
|
||||
if (pid_res.code == 0 && length(pid_res.stdout) > 0) {
|
||||
let pids = split(trim_str(join(' ', pid_res.stdout)), /\s+/);
|
||||
if (length(pids) > 0 && pids[0] != '')
|
||||
return { running: true, pid: +pids[0] };
|
||||
}
|
||||
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
const methods = {};
|
||||
|
||||
methods.get_status = {
|
||||
call: function() {
|
||||
let proc_st = get_procd_status();
|
||||
let status_data = null;
|
||||
|
||||
const status_file = '/tmp/run/cf-optimizer/status.json';
|
||||
if (access(status_file)) {
|
||||
let content = readfile(status_file);
|
||||
if (content) {
|
||||
try {
|
||||
status_data = json(content);
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!status_data) {
|
||||
status_data = {
|
||||
running: proc_st.running,
|
||||
last_update: '',
|
||||
best_ip: '',
|
||||
best_colo: '',
|
||||
best_tcp_lat: '',
|
||||
best_tls_lat: '',
|
||||
domains: [],
|
||||
rankings: []
|
||||
};
|
||||
}
|
||||
|
||||
status_data.service_running = proc_st.running;
|
||||
status_data.service_pid = proc_st.pid || null;
|
||||
|
||||
return status_data;
|
||||
}
|
||||
};
|
||||
|
||||
methods.get_logs = {
|
||||
call: function() {
|
||||
const log_file = '/var/log/cf-optimizer.log';
|
||||
let logs = [];
|
||||
if (access(log_file)) {
|
||||
let res = exec('tail -n 120 ' + log_file + ' 2>/dev/null');
|
||||
if (res.code == 0) logs = res.stdout;
|
||||
}
|
||||
return { logs: logs };
|
||||
}
|
||||
};
|
||||
|
||||
methods.clear_logs = {
|
||||
call: function() {
|
||||
const log_file = '/var/log/cf-optimizer.log';
|
||||
writefile(log_file, '');
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
|
||||
methods.run_once = {
|
||||
call: function() {
|
||||
exec('/usr/sbin/cf-optimizer run >/dev/null 2>&1 &');
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
|
||||
methods.service_action = {
|
||||
args: { action: 'action' },
|
||||
call: function(req) {
|
||||
let action = req.args?.action;
|
||||
if (!action) return { success: false, error: 'Missing action parameter' };
|
||||
|
||||
if (action == 'restart') {
|
||||
exec('/etc/init.d/cf-optimizer restart >/dev/null 2>&1');
|
||||
} else if (action == 'start') {
|
||||
exec('/etc/init.d/cf-optimizer start >/dev/null 2>&1');
|
||||
} else if (action == 'stop') {
|
||||
exec('/etc/init.d/cf-optimizer stop >/dev/null 2>&1');
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
|
||||
return { 'luci.cf-optimizer': methods };
|
||||
Reference in new issue
Block a user