-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.sh
More file actions
326 lines (295 loc) · 9.41 KB
/
Copy pathmain.sh
File metadata and controls
326 lines (295 loc) · 9.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#!/usr/bin/env bash
# @author TomDiary
# @lastTime 2026-03-10
# @version 0.1.0
# 使用 nexttrace 回程脚本(从指定节点 trace 回本机)
# 字体颜色
FONT_BLACK="\033[30m"
FONT_RED="\033[31m"
FONT_GREEN="\033[32m"
FONT_YELLOW="\033[33m"
FONT_BLUE="\033[34m"
FONT_PURPLE="\033[35m"
FONT_SKYBLUE="\033[36m"
FONT_WHITE="\033[37m"
FONT_SUFFIX="\033[0m"
# 背景色(用于节点标题)
BG_BLUE="\033[44m"
BG_CYAN="\033[46m"
NODE_TITLE="${BG_BLUE}${FONT_WHITE}"
# 输出语句颜色
MSG_INFO="${FONT_BLUE} [Info] ${FONT_SUFFIX}"
MSG_WARNING="${FONT_YELLOW} [Warning] ${FONT_SUFFIX}"
MSG_DEG="${FONT_PURPLE} [Debug] ${FONT_SUFFIX}"
MSG_ERROR="${FONT_RED} [Error] ${FONT_SUFFIX}"
MSG_SUCCESS="${FONT_GREEN} [Success] ${FONT_SUFFIX}"
MSG_FAIL="${FONT_SKYBLUE} [Failed] ${FONT_SUFFIX}"
set -e
# 回程IP:4-IPv4,6-IPv6,默认IPv4
TRACE_IPVER=4
# 回程语言:cn-中文,en-英文,默认中文
TRACE_LANG=cn
# 节点数据源:nt=nxtrace(raw GitHub),zc=zstaticcdn
NODES_SOURCE=nt
NODES_CACHE_DIR="${XDG_CACHE_HOME:-/tmp/.besttrace}"
NODES_URL=""
NODES_FILE_LOCAL=""
# 用户中断(Ctrl+C / kill)时退出
on_interrupt() {
echo "" >&2
[ "$TRACE_LANG" = "en" ] && echo "[Info] Interrupted." >&2 || echo "[Info] 已中断" >&2
exit 130
}
trap on_interrupt INT TERM
# 检查并安装 nexttrace
check_nexttrace() {
if command -v nexttrace &>/dev/null; then
return 0
fi
if [ "$TRACE_LANG" = "en" ]; then
echo "[Info] nexttrace not found, attempting install..." >&2
else
echo "[Info] nexttrace 未找到,尝试安装..." >&2
fi
if command -v curl &>/dev/null; then
curl -sL https://nxtrace.org/nt | bash
else
[ "$TRACE_LANG" = "en" ] && echo "[Error] curl required. Install nexttrace: https://nxtrace.org" >&2 || echo "[Error] 需要 curl。请先安装 nexttrace: https://nxtrace.org" >&2
exit 1
fi
}
install_jq() {
if command -v jq &>/dev/null; then
return 0
fi
if [ "$TRACE_LANG" = "en" ]; then
echo "[Info] jq not found, attempting install..." >&2
else
echo "[Info] jq 未找到,尝试自动安装..." >&2
fi
if command -v apt &>/dev/null; then
apt update -y >/dev/null 2>&1 && apt install -y jq >/dev/null 2>&1 || true
elif command -v apt-get &>/dev/null; then
apt-get update -y >/dev/null 2>&1 && apt-get install -y jq >/dev/null 2>&1 || true
elif command -v dnf &>/dev/null; then
dnf install -y jq >/dev/null 2>&1 || true
elif command -v yum &>/dev/null; then
yum install -y jq >/dev/null 2>&1 || true
elif command -v apk &>/dev/null; then
apk add --no-cache jq >/dev/null 2>&1 || true
elif command -v pacman &>/dev/null; then
pacman -Sy --noconfirm jq >/dev/null 2>&1 || true
elif command -v brew &>/dev/null; then
brew install jq >/dev/null 2>&1 || true
fi
if ! command -v jq &>/dev/null; then
if [ "$TRACE_LANG" = "en" ]; then
echo "[Error] Failed to install jq automatically. Please install jq manually." >&2
else
echo "[Error] jq 自动安装失败,请手动安装 jq 后重试。" >&2
fi
exit 1
fi
}
# 打印分隔线
sep() {
printf '%.0s-' {1..70}
echo
}
print_center() {
local text="$1"
local width="$2"
local len=${#text}
local pad=0
if [ "$len" -lt "$width" ]; then
pad=$(( (width - len) / 2 ))
fi
printf "%*s%s\n" "$pad" "" "$text"
}
# 打印帮助信息
usage() {
echo "Usage: $0 [-4] [-6] [-i zc|nt] [-l cn|en]"
echo " -4 IPv4 only (default)"
echo " -6 IPv6 only"
echo " -i zc|nt Nodes: zc=zstaticcdn, nt=nxtrace (default: nt)"
echo " -l cn|en Language: cn=中文, en=English (default: cn)"
echo ""
echo "Example: $0 -6 -l en"
echo "Example: $0 -i zc -4"
}
# 若目标为 host:port 或 [ipv6]:port,则拆出主机与端口(供 --tcp --port)
split_host_port() {
local raw="$1"
if [[ "$raw" =~ ^\[(.+)\]:([0-9]+)$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
printf '%s\n' "${BASH_REMATCH[2]}"
return 0
fi
local colons="${raw//[^:]/}"
if [ "${#colons}" -eq 1 ] && [[ "$raw" =~ ^(.+):([0-9]+)$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
printf '%s\n' "${BASH_REMATCH[2]}"
return 0
fi
printf '%s\n' "$raw"
printf '\n'
return 1
}
apply_nodes_source() {
case "$NODES_SOURCE" in
zc)
NODES_URL="https://raw.githubusercontent.com/tomdiary/besttrace/main/zstaticcdn_nodes.json"
NODES_FILE_LOCAL="${NODES_CACHE_DIR}/nodes-zc.json"
;;
nt | *)
NODES_SOURCE=nt
NODES_URL="https://raw.githubusercontent.com/tomdiary/besttrace/main/nxtrace_nodes.json"
NODES_FILE_LOCAL="${NODES_CACHE_DIR}/nodes-nt.json"
;;
esac
}
load_nodes_from_json() {
local json_path="$NODES_FILE_LOCAL"
local current_source="$NODES_SOURCE"
mkdir -p "$NODES_CACHE_DIR"
install_jq
if command -v curl &>/dev/null; then
local tmp_file="${NODES_FILE_LOCAL}.tmp"
local fetched=0
local urls=()
if [ "$current_source" = "zc" ]; then
urls=(
"https://raw.githubusercontent.com/tomdiary/besttrace/main/zstaticcdn_nodes.json"
"https://cdn.jsdelivr.net/gh/tomdiary/besttrace@main/zstaticcdn_nodes.json"
)
else
urls=(
"https://raw.githubusercontent.com/tomdiary/besttrace/main/nxtrace_nodes.json"
"https://cdn.jsdelivr.net/gh/tomdiary/besttrace@main/nxtrace_nodes.json"
"https://raw.githubusercontent.com/tomdiary/besttrace/main/nodes.json"
)
fi
for u in "${urls[@]}"; do
if curl -fsSL "$u" -o "$tmp_file"; then
if jq -e . "$tmp_file" >/dev/null 2>&1; then
mv "$tmp_file" "$NODES_FILE_LOCAL"
fetched=1
break
fi
fi
rm -f "$tmp_file"
done
if [ "$fetched" -eq 0 ]; then
[ "$TRACE_LANG" = "en" ] \
&& echo "[Warning] Remote nodes JSON unavailable/invalid, keep local cache." >&2 \
|| echo "[Warning] 远程节点 JSON 不可用或无效,保留本地缓存。" >&2
fi
fi
if [ ! -f "$json_path" ]; then
echo "[Error] nodes file not found (remote/local): $NODES_URL / $NODES_FILE_LOCAL" >&2
exit 1
fi
if ! jq -e . "$json_path" >/dev/null 2>&1; then
if [ "$current_source" = "zc" ]; then
[ "$TRACE_LANG" = "en" ] \
&& echo "[Warning] zstaticcdn nodes JSON invalid, fallback to nxtrace nodes." >&2 \
|| echo "[Warning] zstaticcdn 节点 JSON 无效,回退到 nxtrace 节点。" >&2
NODES_SOURCE="nt"
apply_nodes_source
json_path="$NODES_FILE_LOCAL"
if command -v curl &>/dev/null; then
local tmp_file_nt="${NODES_FILE_LOCAL}.tmp"
if curl -fsSL "$NODES_URL" -o "$tmp_file_nt" && jq -e . "$tmp_file_nt" >/dev/null 2>&1; then
mv "$tmp_file_nt" "$NODES_FILE_LOCAL"
else
rm -f "$tmp_file_nt"
fi
fi
fi
fi
if ! jq -e . "$json_path" >/dev/null 2>&1; then
echo "[Error] nodes json parse failed: $json_path" >&2
exit 1
fi
local key name_key
if [ "$TRACE_IPVER" = "6" ]; then
key="nodes_v6"
else
key="nodes_v4"
fi
if [ "$TRACE_LANG" = "en" ]; then
name_key="name_en"
else
name_key="name_cn"
fi
jq -r --arg key "$key" --arg name_key "$name_key" \
'.[$key][] | select(.host != null and .host != "") | "\(.[$name_key] // "")|\(.asn // "")|\(.host)"' \
"$json_path"
}
main() {
apply_nodes_source
if [ "$TRACE_IPVER" = "6" ]; then
NT_IP_FLAG="--ipv6"
else
NT_IP_FLAG="--ipv4"
fi
check_nexttrace
echo ""
if [ "$TRACE_LANG" = "en" ]; then
echo "========================================================================="
print_center "NextTrace Return Route Test - nxtrace.org" 73
print_center "IPv${TRACE_IPVER} | Lang: ${TRACE_LANG}" 73
print_center "v0.1.0 2026-03-10" 73
print_center "Author: TomDiary" 73
print_center "https://github.com/tomdiary/besttrace" 73
echo "========================================================================="
else
echo "========================================================================="
print_center "NextTrace 回程测试 (Return Route) - nxtrace.org" 73
print_center "IPv${TRACE_IPVER} | 语言: ${TRACE_LANG}" 73
print_center "v0.1.0 2026-03-10" 73
print_center "Author: TomDiary" 73
print_center "https://github.com/tomdiary/besttrace" 73
echo "========================================================================="
fi
echo ""
mapfile -t NODES < <(load_nodes_from_json)
for node in "${NODES[@]}"; do
IFS='|' read -ra parts <<< "$node"
name="${parts[0]}"
asn="${parts[1]}"
host="${parts[2]}"
echo "========================================================================="
echo -e "${FONT_YELLOW}$name${FONT_SUFFIX}"
mapfile -t _hp < <(split_host_port "$host")
_h="${_hp[0]}"
_p="${_hp[1]:-}"
if [ -n "$_p" ]; then
nexttrace $NT_IP_FLAG -g "$TRACE_LANG" -M --tcp --port "$_p" "$_h"
else
nexttrace $NT_IP_FLAG -g "$TRACE_LANG" -M "$host"
fi
done
echo "========================================================================="
}
while getopts "46hi:l:" opt; do
case $opt in
4) TRACE_IPVER=4 ;;
6) TRACE_IPVER=6 ;;
i)
case "${OPTARG}" in
zc | nt) NODES_SOURCE="${OPTARG}" ;;
*) echo "Invalid -i option: ${OPTARG}. Use zc or nt."; usage; exit 1 ;;
esac
;;
l)
case "${OPTARG}" in
cn|en) TRACE_LANG="${OPTARG}" ;;
*) echo "Invalid -l option: ${OPTARG}. Use cn or en."; usage; exit 1 ;;
esac
;;
h) usage; exit 0 ;;
*) usage; exit 1 ;;
esac
done
main