forked from Chikage0o0/Linux-NetSpeed
-
Notifications
You must be signed in to change notification settings - Fork 580
Expand file tree
/
Copy pathtcpx.sh
More file actions
3016 lines (2689 loc) · 125 KB
/
Copy pathtcpx.sh
File metadata and controls
3016 lines (2689 loc) · 125 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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
# =================================================
# 全局配置区 (Configuration as Data)
# =================================================
readonly SH_VER="100.0.6.9"
readonly GITHUB_RAW_URL="https://raw.githubusercontent.com/ylx2016/Linux-NetSpeed/master"
# 内核二进制 (bbr/bbrplus/lotserver) 已从仓库迁移到 Release,按目录整包托管,避免仓库臃肿。
readonly KERNEL_RELEASE_BASE="https://github.com/ylx2016/Linux-NetSpeed/releases/download/kernels"
readonly CLOUD_STATE_FILE="/etc/tcpx_cloud_lastver" # installcloud 记忆上次探测到的最高可安装 Cloud 内核
readonly CLOUD_HDR_POOL="https://deb.debian.org/debian/pool/main/l/linux/" # Cloud 内核 headers (arch/-common/kbuild) 所在 pool
# 自安装:把脚本落地到 /usr/local/bin/tcpx,安装后可直接输入 tcpx 运行。
# 以 bash <(curl -fsSL <URL>) 这类管道方式运行时本地没有文件副本,只能从下面的地址重新下载。
# 默认使用与 Update_Shell 相同的权威地址;也可用环境变量 TCPX_URL 临时覆盖:
# TCPX_URL=<地址> bash <(curl -fsSL <地址>)
readonly TCPX_URL_DEFAULT="${GITHUB_RAW_URL}/tcpx.sh"
readonly TCPX_SELF_URL="${TCPX_URL:-$TCPX_URL_DEFAULT}"
readonly TCPX_INSTALL_PATH="/usr/local/bin/tcpx"
# 颜色变量定义
readonly GREEN_FONT_PREFIX="\033[32m"
readonly RED_FONT_PREFIX="\033[31m"
readonly YELLOW_FONT_PREFIX="\033[33m"
readonly FONT_COLOR_SUFFIX="\033[0m"
readonly INFO="${GREEN_FONT_PREFIX}[信息]${FONT_COLOR_SUFFIX}"
readonly ERROR="${RED_FONT_PREFIX}[错误]${FONT_COLOR_SUFFIX}"
readonly TIP="${YELLOW_FONT_PREFIX}[注意]${FONT_COLOR_SUFFIX}"
# 系统信息全局变量 (初始化)
OS_TYPE=""
OS_ID=""
OS_VERSION_ID=""
OS_ARCH=""
# 检查当前用户是否为 root
if [ "$EUID" -ne 0 ]; then
echo -e "${ERROR} 请使用 root 用户身份运行此脚本"
exit 1
fi
# =================================================
# 系统检测模块
# =================================================
check_sys() {
# 1. 检测架构 (使用最通用的 uname)
OS_ARCH=$(uname -m)
# 2. 现代化系统信息获取
if [[ -f /etc/os-release ]]; then
# 直接 source 解析标准的 os-release 文件
. /etc/os-release
OS_ID="${ID:-unknown}"
OS_VERSION_ID="${VERSION_ID:-}"
OS_ID_LIKE="${ID_LIKE:-}" # 新增:获取上游衍生关系
# 兼容 Debian testing/sid 没有 VERSION_ID 的情况
if [[ -z "$OS_VERSION_ID" && "$OS_ID" == "debian" && -f /etc/debian_version ]]; then
OS_VERSION_ID=$(grep -oE '^[0-9]+' /etc/debian_version | head -n 1)
[[ -z "$OS_VERSION_ID" ]] && OS_VERSION_ID=$(awk -F'/' '{print $1}' /etc/debian_version)
fi
[[ -z "$OS_VERSION_ID" ]] && OS_VERSION_ID="unknown"
elif [[ -f /etc/redhat-release || -f /etc/centos-release ]]; then
# 兼容极少数没有 os-release 的老旧 CentOS
OS_ID="centos"
OS_VERSION_ID=$(grep -oE '[0-9.]+' /etc/redhat-release | awk -F'.' '{print $1}')
else
echo -e "${ERROR} 无法检测到受支持的系统版本。此脚本仅支持现代 Debian/Ubuntu/CentOS/Alma/Rocky 系统。"
exit 1
fi
# 3. 规范化 OS_TYPE (引入 ID_LIKE 增强泛衍生版兼容性)
if [[ "$OS_ID" =~ ^(centos|rhel|almalinux|rocky|oracle|fedora)$ ]] || [[ "$OS_ID_LIKE" =~ (rhel|centos|fedora) ]]; then
OS_TYPE="CentOS"
# 提取主版本号
OS_VERSION_ID=$(echo "$OS_VERSION_ID" | awk -F'.' '{print $1}')
elif [[ "$OS_ID" =~ ^(debian|ubuntu|pop|kali|linuxmint|deepin|elementary|zorin|armbian)$ ]] || [[ "$OS_ID_LIKE" =~ (debian|ubuntu) ]]; then
OS_TYPE="Debian"
else
echo -e "${ERROR} 不支持的系统分支: ${OS_ID} (ID_LIKE: ${OS_ID_LIKE})"
exit 1
fi
echo -e "${INFO} 检测到系统: ${OS_TYPE} (${OS_ID} ${OS_VERSION_ID}) - 架构: ${OS_ARCH}"
# 4. 精简依赖检查 (抛弃笨重的 lsb_release;API 的 JSON 解析已改用 grep,不再依赖 jq)
local required_cmds=("curl" "wget" "awk")
if [[ "${OS_TYPE}" == "CentOS" ]]; then
for cmd in "${required_cmds[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo -e "${INFO} 正在安装缺失依赖: $cmd ..."
yum install -y "$cmd" >/dev/null 2>&1
fi
done
# CA 证书更新
if ! rpm -q ca-certificates >/dev/null 2>&1; then
yum install ca-certificates -y >/dev/null 2>&1
update-ca-trust force-enable
fi
elif [[ "${OS_TYPE}" == "Debian" ]]; then
local need_update=0
for cmd in "${required_cmds[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
if [[ $need_update -eq 0 ]]; then
apt-get update >/dev/null 2>&1
need_update=1
fi
echo -e "${INFO} 正在安装缺失依赖: $cmd ..."
apt-get install -y "$cmd" >/dev/null 2>&1
fi
done
# CA 证书更新
if ! dpkg-query -W ca-certificates >/dev/null 2>&1; then
[[ $need_update -eq 0 ]] && apt-get update >/dev/null 2>&1
apt-get install ca-certificates -y >/dev/null 2>&1
update-ca-certificates >/dev/null 2>&1
fi
fi
# 4.1 依赖复检:上面所有安装命令的输出都被重定向丢弃了,装失败也毫无提示。
# 若不在此处硬失败,后续联网抓取会因缺少 curl/wget 而静默失败,
# 用户只能看到"无法获取资源列表"这种完全误导性的报错。
local missing_cmds=()
for cmd in "${required_cmds[@]}"; do
command -v "$cmd" >/dev/null 2>&1 || missing_cmds+=("$cmd")
done
if [[ ${#missing_cmds[@]} -gt 0 ]]; then
echo -e "${ERROR} 以下必需依赖安装失败,脚本无法继续运行:"
for cmd in "${missing_cmds[@]}"; do
echo -e " - ${RED_FONT_PREFIX}${cmd}${FONT_COLOR_SUFFIX}"
done
echo -e "${TIP} 请先手动安装后重试,例如:"
if [[ "${OS_TYPE}" == "CentOS" ]]; then
echo -e " yum install -y ${missing_cmds[*]}"
else
echo -e " apt-get update && apt-get install -y ${missing_cmds[*]}"
fi
exit 1
fi
# 5. 补充底层内核模块管理依赖 (应对极简版 LXC/VPS 模板)
if ! command -v lsmod >/dev/null 2>&1; then
echo -e "${INFO} 正在补齐系统核心依赖: kmod (提供 lsmod/rmmod 命令) ..."
if [[ "${OS_TYPE}" == "CentOS" ]]; then
yum install -y kmod >/dev/null 2>&1
elif [[ "${OS_TYPE}" == "Debian" ]]; then
apt-get install -y kmod >/dev/null 2>&1
fi
fi
}
# =================================================
# 安装前置守卫 (容器环境 / /boot 空间)
# =================================================
# 检测是否运行在容器中。容器共用宿主机内核,装内核毫无意义且可能破坏容器。
check_container() {
local virt=""
if command -v systemd-detect-virt >/dev/null 2>&1; then
virt=$(systemd-detect-virt 2>/dev/null)
elif command -v virt-what >/dev/null 2>&1; then
virt=$(virt-what 2>/dev/null | head -n 1)
fi
case "$virt" in
lxc | lxc-libvirt | openvz | docker | podman | rkt | wsl | systemd-nspawn)
echo -e "${ERROR} 检测到当前为容器环境 (${virt}),容器共用宿主机内核,无法安装内核!"
return 1
;;
esac
# 兜底: 部分老旧 OpenVZ/LXC 无 systemd-detect-virt,用特征文件识别
if [[ -d /proc/vz && ! -d /proc/bc ]]; then
echo -e "${ERROR} 检测到 OpenVZ 容器环境,无法安装内核!"
return 1
fi
if grep -qaE '(lxc|docker|containerd)' /proc/1/cgroup 2>/dev/null; then
echo -e "${ERROR} 检测到容器环境 (cgroup 特征),无法安装内核!"
return 1
fi
return 0
}
# 检查 /boot 剩余空间。小 /boot 在生成 initramfs 阶段中途失败会留下半配置状态,重启即失联。
check_boot_space() {
local need_mb="${1:-300}"
local avail_mb
avail_mb=$(df -Pm /boot 2>/dev/null | awk 'NR==2 {print $4}')
if [[ -z "$avail_mb" ]]; then
echo -e "${TIP} 无法获取 /boot 剩余空间,跳过检查。"
return 0
fi
if [[ "$avail_mb" -lt "$need_mb" ]]; then
echo -e "${ERROR} /boot 剩余空间不足: 当前 ${avail_mb}MB,建议至少 ${need_mb}MB。"
echo -e "${TIP} 内核安装极可能在生成 initramfs 时中途失败,导致系统无法引导!"
echo -e "${TIP} 请先使用菜单 [52] 删除不再使用的旧内核后重试。"
read -rp "确认无视风险并继续?(请输入大写 YES 确认): " force_go
[[ "$force_go" != "YES" ]] && {
echo -e "${INFO} 已取消安装。"
return 1
}
else
echo -e "${INFO} /boot 剩余空间检查通过: ${avail_mb}MB 可用。"
fi
return 0
}
# 内核安装统一前置检查
pre_install_check() {
check_container || return 1
check_boot_space 300 || return 1
return 0
}
# =================================================
# 网络通信与下载模块
# =================================================
# 全局变量:是否在中国大陆
IS_CN=0
# 全局最优镜像变量
BEST_MIRROR=""
# 判断 URL 是否为 GitHub 系资源 (只有这些才需要走加速镜像)
# 非 GitHub 域名 (如 deb.debian.org / snapshot.debian.org) 套镜像前缀必然 404,
# 白白消耗 4 次超时,故必须先行识别。
is_github_url() {
local url="$1"
[[ "$url" =~ ^https?://(www\.)?(github\.com|raw\.githubusercontent\.com|objects\.githubusercontent\.com|codeload\.github\.com)/ ]]
}
# 统一的镜像 URL 组装规则 (测速与实际下载共用,保证两者行为一致)
build_mirror_url() {
local prefix="$1"
local url="$2"
if [[ -z "$prefix" ]]; then
echo "$url"
else
# 镜像站约定: 前缀后直接接不带协议头的原始地址
echo "${prefix}$(echo "$url" | sed 's|^https\?://||')"
fi
}
# 1. 极其稳定且快速的 CN 节点检测 (利用 Cloudflare CDN Trace) 与镜像测速
check_cn_status() {
# 设置 3 秒超时,获取 Cloudflare 边缘节点看到的 IP 归属地
local cf_trace=$(curl -sL --max-time 3 https://www.cloudflare.com/cdn-cgi/trace || echo "")
if echo "$cf_trace" | grep -q "loc=CN"; then
IS_CN=1
echo -e "${INFO} 检测到当前节点位于中国大陆,正在为您测速并选择最快的 GitHub 镜像..."
# 优先测速:只探测 3 个高质量镜像,选最快者作为首选
local mirrors=(
"https://gh-proxy.com/"
"https://ghproxy.net/"
"https://fastgit.cc/"
)
# 使用极小的 releases 校验文件作为测速目标 (不到 100 字节),完美适配镜像站的 release 代理规则
local test_url="https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64.sha256"
local best_time=9999
local failed_mirrors=()
for prefix in "${mirrors[@]}"; do
# 与 safe_wget 保持完全一致的 URL 改写规则,确保测速结果能代表实际下载可用性
local target_url=$(build_mirror_url "$prefix" "$test_url")
# 测速: 限定最多 2 秒超时,-r 0-512 限制只读取头部数据,防止大文件卡死
# 同时取回 http_code,避免"快速返回 403/404 错误页"的失效镜像在测速中胜出
local probe=$(curl -sL -m 2 -r 0-512 -o /dev/null -w "%{http_code} %{time_total}" "$target_url" 2>/dev/null)
local http_code=$(echo "$probe" | awk '{print $1}')
local time_cost=$(echo "$probe" | awk '{print $2}')
# 仅 200/206 视为有效;其余(含连接失败的空输出)一律记为失败节点
if [[ ! "$http_code" =~ ^(200|206)$ ]] || [[ -z "$time_cost" ]]; then
failed_mirrors+=("$prefix")
else
# 使用 awk 比较浮点数时间,找出延迟最低的
if awk "BEGIN {exit !($time_cost < $best_time)}"; then
best_time=$time_cost
BEST_MIRROR=$prefix
fi
fi
done
if [[ "$best_time" == "9999" ]]; then
echo -e "${TIP} 所有镜像测速超时,将使用默认轮询模式。"
BEST_MIRROR="poll"
else
echo -e "${INFO} 测速完成!当前网络最优镜像为: ${GREEN_FONT_PREFIX}${BEST_MIRROR}${FONT_COLOR_SUFFIX} (响应时间: ${best_time}s)"
fi
# 集中打印出测速失败的镜像,方便您排查和后续替换
if [[ ${#failed_mirrors[@]} -gt 0 ]]; then
echo -e "${TIP} 以下镜像测速超时或失效 (已自动剔除首选):"
for fail_m in "${failed_mirrors[@]}"; do
echo -e " - ${RED_FONT_PREFIX}${fail_m}${FONT_COLOR_SUFFIX}"
done
fi
else
IS_CN=0
echo -e "${INFO} 当前节点位于海外,使用 GitHub 直连网络。"
fi
}
# 2. 安全可靠的下载函数 (自带多镜像轮询 failover,支持动态最优节点)
safe_wget() {
local url="$1"
local dest="$2"
local timeout=15
# 定义默认的加速镜像前缀池
# 下载兜底池:3 个高质量镜像在前,其余可用镜像随后
local mirrors=(
"https://gh-proxy.com/"
"https://ghproxy.net/"
"https://fastgit.cc/"
"https://githubdog.com/"
"https://tvv.tw/"
"https://ghfast.top/"
)
# 核心逻辑:根据测速结果重构下载队列
# 非 GitHub 资源 (Debian 官方源等) 直接直连,套镜像只会白等 4 次超时
if [[ $IS_CN -eq 0 ]] || ! is_github_url "$url"; then
mirrors=("") # 海外节点 / 非 GitHub 资源只保留原生空前缀
else
# 如果测速成功找到了最优节点,将其提取到数组最前面
if [[ -n "$BEST_MIRROR" && "$BEST_MIRROR" != "poll" ]]; then
local new_mirrors=("$BEST_MIRROR")
for m in "${mirrors[@]}"; do
[[ "$m" != "$BEST_MIRROR" ]] && new_mirrors+=("$m")
done
mirrors=("${new_mirrors[@]}")
fi
# 给国内机加上原生链接作为终极保底
mirrors+=("")
fi
for prefix in "${mirrors[@]}"; do
# 组装最终下载链接 (与测速阶段共用同一套规则)
local target_url=$(build_mirror_url "$prefix" "$url")
if [[ -z "$prefix" ]]; then
echo -e "${INFO} 正在请求: ${dest} (直连网络) ..."
else
echo -e "${INFO} 正在请求: ${dest} (镜像: ${prefix}) ..."
fi
# 使用 wget 下载,跳过证书校验
if wget --no-check-certificate -qT "$timeout" -t 2 -O "$dest" "$target_url"; then
# 防御半截包/错误页: 空文件视为失败,继续切换节点
if [[ -s "$dest" ]]; then
echo -e "${INFO} ${dest} 下载成功!"
return 0
fi
echo -e "${TIP} 下载内容为空,判定为无效响应。"
rm -f "$dest"
fi
# 当前节点失败时提示并继续下一个循环
echo -e "${TIP} 当前节点下载异常,正在无缝切换备用节点..."
done
echo -e "${ERROR} 文件 ${dest} 所有下载节点均失效,请检查网络或稍后再试!"
return 1
}
# 3. 稳健的 GitHub 资源获取函数 (用 grep 从 JSON 提取下载直链,再通过 grep 多重过滤)
# 用法: get_github_asset <仓库名> <Tag关键词> <文件名关键词>
# 示例: get_github_asset "ylx2016/kernel" "Debian_Kernel" "headers"
get_github_asset() {
local repo="$1"
local tag_kw="$2"
local ast_kw="$3"
local arch_kw="$4" # 可选的架构关键词
local api_url="https://api.github.com/repos/${repo}/releases"
local response=$(curl -sL --max-time 10 "$api_url")
if echo "$response" | grep -q "API rate limit exceeded"; then
echo -e "${ERROR} 触发 GitHub API 频率限制!(当前 IP 请求过多)" >&2
return 1
fi
# 提取出该仓库所有的下载直链。
# 原用 jq,现改为纯 grep:browser_download_url 字段仅出现在 release 资源(assets)里,
# 且 GitHub API 不转义 URL 中的斜杠,故按字段名精确匹配后取其 https 值即可,
# 效果等价于 jq -r '.[].assets[]?.browser_download_url',同时免去 jq (及 CentOS 的 EPEL) 依赖。
local all_urls=$(echo "$response" | grep -oE '"browser_download_url":[[:space:]]*"[^"]+"' | grep -oE 'https?://[^"]+')
if [[ -z "$all_urls" ]]; then
echo -e "${ERROR} 无法从 ${repo} 获取资源列表,请检查网络或稍后再试!" >&2
return 1
fi
# 利用 grep -iE 进行层层精准过滤
local result=$(echo "$all_urls" | grep -iE "$tag_kw" | grep -iE "$ast_kw")
[[ -n "$arch_kw" ]] && result=$(echo "$result" | grep -iE "$arch_kw")
# 排除调试符号包与未签名包:dbg 包体积可达数百 MB 且完全不可用于安装,
# 而 grep "image" 会同时命中 linux-image-*-dbg_*.deb,head -n 1 有取错风险
result=$(echo "$result" | grep -viE 'dbg|debug|unsigned')
# 终极防呆机制:如果是 x86_64 架构,且关键词中没有声明要找 arm64,则强行排除带 arm64/aarch64 的链接,防止模糊匹配误伤
if [[ "$arch_kw" != *"arm64"* && "$tag_kw" != *"arm64"* && "$OS_ARCH" != "aarch64" ]]; then
result=$(echo "$result" | grep -viE "arm64|aarch64")
fi
local asset_url=$(echo "$result" | head -n 1)
if [[ -z "$asset_url" ]]; then
echo -e "${ERROR} 无法在 ${repo} 中解析到匹配关键字 (${tag_kw} -> ${ast_kw} -> ${arch_kw}) 的文件!" >&2
return 1
fi
echo "$asset_url"
}
# =================================================
# 内核安装核心引擎
# =================================================
# 清理旧的 Headers (精简重构)
remove_old_headers() {
echo -e "${INFO} 正在清理旧的内核 Headers 防止冲突..."
if [[ "${OS_TYPE}" == "CentOS" ]]; then
# 找出不是当前正在运行的 kernel-headers 并卸载
local current_ker=$(uname -r)
rpm -qa | grep -E 'kernel(-ml|-lt|-uek|-rt|-plus)?-headers' | grep -v "$current_ker" | xargs -r rpm -e --nodeps >/dev/null 2>&1
elif [[ "${OS_TYPE}" == "Debian" ]]; then
dpkg -l | grep -E '(linux|proxmox|pve|raspberrypi)-headers' | awk '{print $2}' | grep -v "$(uname -r)" | xargs -r apt-get purge -y >/dev/null 2>&1
apt-get autoremove -y >/dev/null 2>&1
fi
}
# 终极内核安装函数
# 用法: install_kernel_generic <内核描述名称> <Headers_URL> <Image_URL> <版本号>
install_kernel_generic() {
local kernel_desc="$1"
local head_url="$2"
local img_url="$3"
local kernel_version="$4" # 新增参数,用于 UI 显示
echo -e "${INFO} ================================================"
if [[ -n "$kernel_version" ]]; then
echo -e "${INFO} 开始安装: ${kernel_desc} (版本: \033[32m${kernel_version}\033[0m)"
else
echo -e "${INFO} 开始安装: ${kernel_desc}"
fi
echo -e "${INFO} ================================================"
# 只强制检查 img_url,因为某些内核(如 Cloud)本身可能不强制要求 Headers
if [[ -z "$img_url" ]]; then
echo -e "${ERROR} 传入的镜像文件下载链接为空,可能是 API 解析失败或上游移除了文件!"
return 1
fi
# 创建独立的工作目录 (mktemp 避免 /tmp 下可预测目录名被抢先创建)
local work_dir
work_dir=$(mktemp -d /tmp/kernel_install.XXXXXX) || {
echo -e "${ERROR} 无法创建临时目录!"
return 1
}
# apt 会用沙箱用户 _apt 访问此目录;700 权限会触发 "unsandboxed" 警告。
# 755 仅允许读/遍历,.deb 文件本身仍由 root 拥有,不存在安全问题。
chmod 755 "$work_dir"
trap 'cd /tmp; rm -rf "$work_dir"; trap - RETURN' RETURN
cd "$work_dir" || {
echo -e "${ERROR} 无法进入临时目录 ${work_dir}!"
return 1
}
# 安装前记录 /boot 中已有的内核镜像数量,用于事后验证是否真的装上了
local vmlinuz_before=$(ls -1 /boot/vmlinuz-* 2>/dev/null | wc -l)
# 根据系统执行不同的下载和安装逻辑
# 关键顺序: 先把所有包下载并校验完成,再清理旧 headers,最后安装。
# 若先清理后下载,一旦下载失败就会停在"旧 headers 已删、新内核没装"的破损状态。
if [[ "${OS_TYPE}" == "CentOS" ]]; then
local head_file="kernel-headers.rpm"
local img_file="kernel-image.rpm"
[[ -n "$head_url" ]] && { safe_wget "$head_url" "$head_file" || return 1; }
safe_wget "$img_url" "$img_file" || return 1
# 下载全部成功后才动系统
remove_old_headers
echo -e "${INFO} 正在执行 YUM 安装..."
if [[ -n "$head_url" ]]; then
yum install -y "./$img_file" "./$head_file" || {
echo -e "${ERROR} 内核包安装失败,请检查上方 yum 输出!"
return 1
}
else
yum install -y "./$img_file" || {
echo -e "${ERROR} 内核包安装失败,请检查上方 yum 输出!"
return 1
}
fi
elif [[ "${OS_TYPE}" == "Debian" ]]; then
local head_file="linux-headers.deb"
local img_file="linux-image.deb"
local deb_list=()
[[ -n "$head_url" ]] && { safe_wget "$head_url" "$head_file" || return 1; }
safe_wget "$img_url" "$img_file" || return 1
# 下载全部成功后才动系统
remove_old_headers
deb_list+=("./$img_file")
[[ -n "$head_url" && -s "$head_file" ]] && deb_list+=("./$head_file")
# 用 apt-get install ./xxx.deb 取代 dpkg -i + apt-get -f。
# 前者是事务性的: 会自动从源里解析 linux-modules-* 等依赖,失败时干净回滚;
# 后者在依赖无解时 apt-get -f 可能选择"卸载刚装上的内核"来修复,且返回值常为 0。
echo -e "${INFO} 正在执行 APT 安装 (自动解析依赖)..."
if ! DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${deb_list[@]}"; then
echo -e "${ERROR} 内核包安装失败,依赖无法满足或包不兼容当前系统!"
echo -e "${TIP} 系统未被改动,可尝试菜单 [4] 中的 'a' 自动探测可安装版本。"
return 1
fi
fi
# 验证内核镜像确实就位,避免"装失败却报成功"。
# apt-get install ./deb 已返回成功,说明包已在系统里 (新装或早已安装);
# 这里只防御 apt-get -f 把刚装的内核又自动卸掉的情况——那会让镜像数"不增反减"。
# 数目持平属正常 (目标内核本就已安装的幂等重跑),不算失败,需继续处理 headers。
local vmlinuz_after=$(ls -1 /boot/vmlinuz-* 2>/dev/null | wc -l)
if [[ "$vmlinuz_after" -lt "$vmlinuz_before" ]]; then
echo -e "${ERROR} /boot 中的内核镜像不增反减,安装可能被回滚 (依赖冲突)!"
echo -e "${TIP} 请勿重启,先用菜单 [51] 确认当前内核状态。"
return 1
fi
[[ "$vmlinuz_after" -eq "$vmlinuz_before" ]] && echo -e "${INFO} 目标内核镜像已存在 (无需重复安装),继续处理配套组件..."
echo -e "${INFO} ${kernel_desc} 内核包安装完成,正在更新系统引导..."
BBR_grub
}
# 安装 BBR 原版内核 (调用引擎)
installbbr() {
pre_install_check || return 1
local head_url=""
local img_url=""
local tag_kw=""
local arch_kw=""
local img_kw=""
if [[ "${OS_TYPE}" == "CentOS" ]]; then
# 适配新编译的 CentOS Cloud 内核
tag_kw="CentOS_Kernel_Cloud"
arch_kw="x86_64"
# 【核心修复 1】直接匹配 kernel-加数字,从源头完美避开 headers 和 devel
img_kw="kernel-[0-9]"
elif [[ "${OS_TYPE}" == "Debian" ]]; then
# 适配新编译的 Debian Cloud 内核
tag_kw="Debian_Kernel_Cloud"
arch_kw="amd64"
img_kw="image" # Debian dpkg 生成的包名为 linux-image 开头
if [[ "$OS_ARCH" == "aarch64" ]]; then
tag_kw="Debian_Kernel_Cloud_arm64"
arch_kw="arm64"
fi
fi
echo -e "${INFO} 正在向 Github/ylx2016 请求最新 ${tag_kw} 内核数据..."
# 获取下载链接 (现在抓取到的绝对纯净,无需二次 grep -v)
head_url=$(get_github_asset "ylx2016/kernel" "${tag_kw}" "headers" "${arch_kw}")
img_url=$(get_github_asset "ylx2016/kernel" "${tag_kw}" "${img_kw}" "${arch_kw}")
# 【核心修复 2】版本号仅用于界面显示:只从文件名 (${img_url##*/}) 中提取,切勿扫描整条 URL。
# 因为发布 tag 内含构建日期 (如 Debian_Kernel_Cloud_7.2_bbr_2026.08.18-0005),整条 URL 的首个
# N.N.N 会误命中日期 2026.08.18;而文件名 linux-image-7.2.0_... 的首个 N.N.N 才是真内核版本。
# 用 -oE 标准正则 (而非 -P) 以兼容不支持 PCRE 的老系统。
local kernel_version=$(echo "${img_url##*/}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
if [[ -n "$kernel_version" ]]; then
echo -e "${INFO} 解析成功!获取到的最新云端内核版本为: \033[32m${kernel_version}\033[0m"
else
echo -e "${INFO} 解析成功!已获取到下载链接,但未能匹配出纯净版本号。"
fi
# 将解析出的版本号作为第四个参数传递给安装函数
install_kernel_generic "BBR Cloud 优化内核" "$head_url" "$img_url" "$kernel_version"
}
# 安装 BBRplus 新版内核 (调用引擎)
installbbrplusnew() {
pre_install_check || return 1
local head_url=""
local img_url=""
local tag_kw="bbrplus-6."
local ext="deb"
local arch_kw="amd64"
# Debian 的包名为 linux-image-*,而 RPM 的包名是 kernel-<版本号>,二者必须区分
local img_kw="image"
if [[ "${OS_TYPE}" == "CentOS" ]]; then
ext="rpm"
arch_kw="x86_64" # RPM 文件名用 x86_64,沿用 amd64 会过滤为空
img_kw="kernel-[0-9]" # 直接匹配 kernel-加数字,从源头避开 headers/devel
[[ "$OS_ARCH" == "aarch64" ]] && arch_kw="aarch64"
else
[[ "$OS_ARCH" == "aarch64" ]] && arch_kw="arm64"
fi
echo -e "${INFO} 正在向 UJX6N/bbrplus-6.x_stable 请求数据..."
# 利用精准的参数向下传递
head_url=$(get_github_asset "UJX6N/bbrplus-6.x_stable" "${tag_kw}" "headers" "${arch_kw}.*${ext}")
img_url=$(get_github_asset "UJX6N/bbrplus-6.x_stable" "${tag_kw}" "${img_kw}" "${arch_kw}.*${ext}")
local kernel_version=$(echo "${img_url##*/}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1)
install_kernel_generic "BBRplus(UJX6N)新版内核" "$head_url" "$img_url" "$kernel_version"
}
# 安装 BBRplus 内核 4.14.129 (cx9208版)
installbbrplus() {
pre_install_check || return 1
local head_url=""
local img_url=""
if [[ "${OS_TYPE}" == "CentOS" && "${OS_VERSION_ID}" == "7" ]]; then
head_url="https://github.com/cx9208/Linux-NetSpeed/raw/master/bbrplus/centos/7/kernel-headers-4.14.129-bbrplus.rpm"
img_url="https://github.com/cx9208/Linux-NetSpeed/raw/master/bbrplus/centos/7/kernel-4.14.129-bbrplus.rpm"
elif [[ "${OS_TYPE}" == "Debian" && "${OS_ARCH}" == "x86_64" ]]; then
head_url="https://github.com/cx9208/Linux-NetSpeed/raw/master/bbrplus/debian-ubuntu/x64/linux-headers-4.14.129-bbrplus.deb"
img_url="https://github.com/cx9208/Linux-NetSpeed/raw/master/bbrplus/debian-ubuntu/x64/linux-image-4.14.129-bbrplus.deb"
else
echo -e "${ERROR} BBRplus 4.14.129 仅支持 CentOS 7 或 Debian x86_64!"
return 1
fi
install_kernel_generic "BBRplus 4.14.129" "$head_url" "$img_url"
}
# =================================================
# 自动探测 Cloud 内核最高可安装版本 (依赖解析)
# =================================================
# 抓取 pool 目录清单,抽出所有 headers/kbuild 的 .deb 文件名 (供 headers 解析/校验复用)
fetch_cloud_pool_listing() {
curl -sL --max-time 15 "$CLOUD_HDR_POOL" | grep -oE '(linux-headers|linux-kbuild)-[^"]+\.deb'
}
# 由 cloud image 文件名推导配套的三件 headers 包 (arch专属 + -common + kbuild) 文件名。
# 参数: <image文件名> <debarch> <pool清单>;每行输出一个清单中确实存在的 .deb 文件名;无 arch headers 时返回 1。
resolve_cloud_header_debs() {
local img_file="$1" debarch="$2" listing="$3"
local abi base
abi=$(echo "$img_file" | sed -nE "s/^linux-image-(.*-cloud-${debarch})_.*/\1/p")
[[ -z "$abi" ]] && return 1
base="${abi%-cloud-${debarch}}" # 6.17.13+deb13-cloud-amd64 -> 6.17.13+deb13
# 先用 arch 专属 headers 定位版本号,再据此锁定同版本 common / kbuild,避免版本错配
local arch_hdr ver
arch_hdr=$(printf '%s\n' "$listing" | while read -r f; do
case "$f" in "linux-headers-${abi}_"*"_${debarch}.deb") echo "$f";; esac
done | sort -V | tail -n 1)
[[ -z "$arch_hdr" ]] && return 1
ver="${arch_hdr#linux-headers-${abi}_}"; ver="${ver%_${debarch}.deb}"
echo "$arch_hdr"
local common_hdr="linux-headers-${base}-common_${ver}_all.deb"
local kbuild="linux-kbuild-${base}_${ver}_${debarch}.deb"
printf '%s\n' "$listing" | grep -qxF "$common_hdr" && echo "$common_hdr"
printf '%s\n' "$listing" | grep -qxF "$kbuild" && echo "$kbuild"
return 0
}
# 模拟校验某 cloud 内核的配套 headers 三件套依赖能否干净满足 (口径与镜像校验一致)。
# 参数: <image文件名> <debarch> <pool清单> <下载工作目录>;可满足返回 0,否则返回 1。
check_cloud_headers_installable() {
local img_file="$1" debarch="$2" listing="$3" scan_dir="$4"
local hdr_files
hdr_files=$(resolve_cloud_header_debs "$img_file" "$debarch" "$listing") || return 1
[[ -z "$hdr_files" ]] && return 1
local debs=() hf
while read -r hf; do
[[ -z "$hf" ]] && continue
safe_wget "${CLOUD_HDR_POOL}${hf}" "${scan_dir}/${hf}" >/dev/null 2>&1 || return 1
debs+=("${scan_dir}/${hf}")
done <<< "$hdr_files"
[[ ${#debs[@]} -eq 0 ]] && return 1
local log
log=$(LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -s --no-install-recommends "${debs[@]}" 2>&1)
rm -f "${debs[@]}"
echo "$log" | grep -qiE "unmet dependencies|broken packages|E: " && return 1
echo "$log" | grep -q "will be REMOVED" && return 1
if echo "$log" | grep -q "will be upgraded"; then
local upgraded=$(echo "$log" | sed -n '/will be upgraded/,/^[^ ]/p' | tail -n +2)
echo "$upgraded" | grep -qwE "libc6|base-files|dpkg|systemd|systemd-sysv|bash|login|tzdata" && return 1
fi
return 0
}
# 用法: detect_cloud_max <img_url_base> <need_headers:0|1> <debarch> <file1> <file2> ...
# 原理: pool 中混着 stable/testing/sid/experimental 全部内核,版本号最大 ≠ 能装。
# 太新的内核依赖独立的 linux-modules-*-cloud-* 包(只存在于其自身发行版源),
# 并可能要求更新的 linux-base / initramfs-tools / libc6。因此从新到旧逐个
# 下载 image 包交给 `apt-get install -s` 做模拟安装(真实依赖解析器),第一个
# “干净解析”的版本即当前系统最高可安装版本。
# need_headers=1 时还会额外模拟其配套 Headers 三件套,镜像与 Headers 都干净才选中,
# 以此避开 +debN backports 这类"镜像能装、Headers 因 glibc/工具链过新装不了"的内核。
# 结果写入全局变量 CLOUD_MAX_IDX / CLOUD_MAX_FILE (仍为 -1 / 空 表示无可用版本)
CLOUD_MAX_IDX=-1
CLOUD_MAX_FILE=""
detect_cloud_max() {
local img_url_base="$1"
local need_headers="$2" # 1 = 同时要求配套 headers 依赖也可满足
local debarch="$3"
shift 3
local versions_array=("$@")
CLOUD_MAX_IDX=-1
CLOUD_MAX_FILE=""
local scan_work
scan_work=$(mktemp -d /tmp/cloud_kernel_scan.XXXXXX) || return 1
trap 'cd /tmp; rm -rf "$scan_work"; trap - RETURN' RETURN
cd "$scan_work" || return 1
# 需要连 headers 一起校验时,预取一次 pool 清单循环复用 (所有版本共用同一 pool)
local hdr_listing=""
[[ "$need_headers" == "1" ]] && hdr_listing=$(fetch_cloud_pool_listing)
# 提速优化:candidate 文件名里的 +debN 表示该内核面向哪个 Debian 大版本。
# 当前系统若是纯 Debian,凡 +debN 高于本机大版本的候选,其 Headers 必然依赖更新的
# glibc/工具链 (如 deb13 headers 需 glibc≥2.38 而 deb12 只有 2.36) — 直接跳过不下载,
# 免去逐个下载+apt 模拟才发现装不上的耗时。仅在 need_headers 时启用 (只装镜像时高版本仍可用)。
# Ubuntu 等衍生系 OS_VERSION_ID 语义不同 (如 24.04) 无法与 debN 比较,故仅限 OS_ID=debian。
local host_deb=""
if [[ "$need_headers" == "1" && "${OS_ID}" == "debian" ]]; then
host_deb="${OS_VERSION_ID%%.*}"
[[ "$host_deb" =~ ^[0-9]+$ ]] || host_deb=""
fi
for ((i = ${#versions_array[@]} - 1; i >= 0; i--)); do
local f="${versions_array[$i]}"
if [[ -n "$host_deb" ]]; then
local cand_deb=$(echo "$f" | grep -oE '\+deb[0-9]+' | grep -oE '[0-9]+')
if [[ -n "$cand_deb" && "$cand_deb" -gt "$host_deb" ]]; then
echo -e "${TIP} ✗ ${f} 面向 Debian ${cand_deb} (高于当前 Debian ${host_deb}),Headers 依赖更新工具链,快速跳过"
continue
fi
fi
echo -e "${INFO} 正在探测候选内核: ${f} ..."
if ! safe_wget "${img_url_base}${f}" "probe.deb" >/dev/null 2>&1; then
continue
fi
# 校验是合法 ar 归档,防止半截包造成误判
if ! head -c 8 probe.deb | grep -q "!<arch>"; then
rm -f probe.deb
continue
fi
# 必须强制 LC_ALL=C: 下面全部靠 grep apt 的英文输出判断依赖是否可满足,
# 在 zh_CN 等非英文 locale 下 apt 输出被翻译,所有 grep 都匹配不到,
# 会导致每个版本都被误判为"依赖可干净满足",本函数彻底失效。
local scan_log=$(LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -s --no-install-recommends "./probe.deb" 2>&1)
rm -f probe.deb
local ok=1
# 1) 依赖无法满足 / 包破损
if echo "$scan_log" | grep -qiE "unmet dependencies|broken packages|E: "; then
ok=0
fi
# 2) 会卸载现有软件包 = 必然冲突
if echo "$scan_log" | grep -q "will be REMOVED"; then
ok=0
fi
# 3) 会升级到跨发行版核心基础库 (libc6/base-files/dpkg/systemd) = 硬装,跳过。
# 允许升级 linux-base/initramfs-tools 等内核生态包 (当前源内可正常解决)
if echo "$scan_log" | grep -q "will be upgraded"; then
local upgraded=$(echo "$scan_log" | sed -n '/will be upgraded/,/^[^ ]/p' | tail -n +2)
if echo "$upgraded" | grep -qwE "libc6|base-files|dpkg|systemd|systemd-sysv|bash|login|tzdata"; then
ok=0
fi
fi
if [[ $ok -eq 1 ]]; then
# 镜像依赖 OK;若还要求 headers,则继续校验配套 headers 依赖,两者都过才收下
if [[ "$need_headers" == "1" ]]; then
echo -e "${GREEN_FONT_PREFIX} ✓ 镜像依赖可满足${FONT_COLOR_SUFFIX},继续校验配套 Headers 依赖..."
if check_cloud_headers_installable "$f" "$debarch" "$hdr_listing" "$scan_work"; then
CLOUD_MAX_IDX=$i
CLOUD_MAX_FILE="$f"
echo -e "${GREEN_FONT_PREFIX} ✓ 镜像 + Headers 依赖均可干净满足${FONT_COLOR_SUFFIX}"
break
else
echo -e "${TIP} ✗ 该版本 Headers 依赖不满足 (多为跨发行版内核),继续找更旧版本"
fi
else
CLOUD_MAX_IDX=$i
CLOUD_MAX_FILE="$f"
echo -e "${GREEN_FONT_PREFIX} ✓ 该版本依赖可干净满足${FONT_COLOR_SUFFIX}"
break
fi
else
echo -e "${TIP} ✗ 该版本依赖冲突,已跳过"
fi
done
# 临时目录由 RETURN trap 统一清理
}
# 确保 Debian Cloud 内核的配套 Headers 完整安装 (arch 专属 + -common + linux-kbuild)。
# 内核镜像单独安装 (installcloud 给 install_kernel_generic 传空 headers),headers 改在此处从 pool
# 直取三件套后离线 apt 安装;这样即便是 backports (~bpoN) 内核、系统又没启用 backports 源,也能装全依赖。
ensure_cloud_headers() {
local img_file="$1"
local debarch="$2"
# 从镜像文件名提取 ABI,如 6.17.13+deb13-cloud-amd64
local abi
abi=$(echo "$img_file" | sed -nE "s/^linux-image-(.*-cloud-${debarch})_.*/\1/p")
[[ -z "$abi" ]] && return 0
# 已存在编译用的 build 目录 (headers 完整) 则无需再动
if [[ -d "/lib/modules/${abi}/build" || -d "/usr/src/linux-headers-${abi}" ]]; then
echo -e "${INFO} 内核 ${abi} 的配套 Headers 已就绪,可直接编译内核模块。"
return 0
fi
echo -e "${INFO} 正在为内核 ${abi} 补齐配套 Headers (arch + -common + kbuild,从 pool 直取以兼容 backports)..."
local listing hdr_files
listing=$(fetch_cloud_pool_listing)
hdr_files=$(resolve_cloud_header_debs "$img_file" "$debarch" "$listing")
if [[ -z "$hdr_files" ]]; then
echo -e "${TIP} pool 中未找到 ${abi} 的 headers,已跳过 (内核镜像不受影响,仅暂无法编译模块)。"
return 0
fi
# 下载三件套到临时目录,一次性 apt 离线安装 (依赖自洽,无需启用 backports 源)
local work_dir debs=() f
work_dir=$(mktemp -d /tmp/cloud_hdr.XXXXXX) || { echo -e "${TIP} 无法创建临时目录,已跳过 headers。"; return 0; }
chmod 755 "$work_dir"
while read -r f; do
[[ -z "$f" ]] && continue
safe_wget "${CLOUD_HDR_POOL}${f}" "${work_dir}/${f}" && debs+=("${work_dir}/${f}")
done <<< "$hdr_files"
local apt_out rc=1
if [[ ${#debs[@]} -gt 0 ]]; then
apt_out=$(DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${debs[@]}" 2>&1)
rc=$?
fi
if [[ $rc -eq 0 ]]; then
echo -e "${INFO} 配套 Headers 已装好,可正常编译 LotSpeed / brutal 等内核模块。"
elif printf '%s' "$apt_out" | grep -qE 'libc6 \(>= 2\.(3[89]|[4-9])|t64 |gcc-[0-9]+-for-host'; then
# 跨发行版内核 (如 +debN backports) 的 Headers 依赖更新的 glibc/工具链,当前较旧系统满足不了。
# 内核镜像本身是自包含的能装上,但 Headers 编译环境装不了——与之前 LotSpeed 的 GLIBC_2.38 同源。
echo -e "${TIP} Headers 无法安装:内核 ${abi} 是较新发行版的 backports 版,其 Headers 依赖更新的 glibc/工具链,"
echo -e "${TIP} 与当前系统 (Debian $(cat /etc/debian_version 2>/dev/null)) 不兼容。内核镜像不受影响。"
echo -e "${TIP} 建议:仅用 BBR 无需 Headers,重启进新内核即可;若要编译模块,请改装与本系统同发行版的 Cloud 内核"
echo -e "${TIP} (菜单里选不带 +debN 的版本,或输入 'h' 走 apt 装配套 headers)。"
else
echo -e "${TIP} Headers 安装未完成 (下载失败或依赖冲突),内核镜像不受影响;如需编译请重启后手动安装。"
fi
rm -rf "$work_dir"
}
# 安装官方 Cloud 内核
installcloud() {
pre_install_check || return 1
[[ "${OS_TYPE}" != "Debian" ]] && {
echo -e "${ERROR} Cloud 内核仅支持 Debian 系系统"
return 1
}
# Ubuntu 及衍生版:没有 Debian 那套 pool/+debN 云内核,逐版本探测无从谈起 (deb.debian.org
# 的包名与 Ubuntu apt 对不上,'linux-image-cloud-amd64' 在 Ubuntu 也不存在)。
# Ubuntu 的云/虚拟机等价物是 apt 元包 linux-image-virtual,直接走 apt 最稳妥。
if [[ "${OS_ID}" == "ubuntu" || "${OS_ID}" == "pop" || "${OS_ID_LIKE}" == *"ubuntu"* ]]; then
echo -e "${INFO} 检测到 Ubuntu/衍生系:Cloud 内核对应 apt 元包 linux-image-virtual (随发行版滚动更新)。"
local u_ans u_hdr=""
read -e -p "是否同时安装配套 Headers?(编译内核模块才需要) [y/N]: " u_ans
[[ "$u_ans" =~ ^[yY]$ ]] && u_hdr="linux-headers-virtual"
apt-get update >/dev/null 2>&1
if apt-get install -y linux-image-virtual $u_hdr; then
echo -e "${INFO} Ubuntu 云内核 (linux-image-virtual${u_hdr:+ + linux-headers-virtual}) 安装完成。"
BBR_grub
else
echo -e "${ERROR} apt 安装 linux-image-virtual 失败,请检查网络与 apt 源。"
return 1
fi
return 0
fi
local debarch
if [[ "$OS_ARCH" == "x86_64" ]]; then
debarch="amd64"
elif [[ "$OS_ARCH" == "aarch64" ]]; then
debarch="arm64"
else
echo -e "${ERROR} 不支持的架构:$OS_ARCH"
return 1
fi
local img_url_base="https://deb.debian.org/debian/pool/main/l/linux-signed-${debarch}/"
local img_pattern='linux-image-[^" ]+cloud-'"${debarch}"'_[^" ]+_'"${debarch}"'\.deb'
echo -e "${INFO} 正在从 Debian 官方源获取 Cloud 内核列表..."
local deb_files=$(curl -sL --max-time 10 "$img_url_base" | grep -oE "$img_pattern" | sort -V | uniq)
if [[ -z "$deb_files" ]]; then
echo -e "${ERROR} 未找到可用的 Cloud 内核版本,请检查网络!"
return 1
fi
mapfile -t versions_array <<<"$deb_files"
# ---- 读取上次记录的最高可安装版本 (用于默认选中与高亮提示) ----
local remembered=""
[[ -f "$CLOUD_STATE_FILE" ]] && remembered=$(cat "$CLOUD_STATE_FILE" 2>/dev/null)
local remembered_idx=-1
if [[ -n "$remembered" ]]; then
for i in "${!versions_array[@]}"; do
[[ "${versions_array[$i]}" == "$remembered" ]] && {
remembered_idx=$i
break
}
done
fi
# ---- 展示全部版本 (保持原始行为) ----
echo -e "${INFO} 检测到以下 Cloud 内核版本 (pool 全部候选):"
for i in "${!versions_array[@]}"; do
local v_show=$(echo "${versions_array[$i]}" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+-[0-9]+')
if [[ $i -eq $remembered_idx ]]; then
echo -e " ${GREEN_FONT_PREFIX}$i) [$v_show]${FONT_COLOR_SUFFIX} ${RED_FONT_PREFIX}← 上次最高可安装版本${FONT_COLOR_SUFFIX} -> ${versions_array[$i]}"
else
echo " $i) [$v_show] -> ${versions_array[$i]}"
fi
done
echo -e " ${GREEN_FONT_PREFIX}a) 自动逐个探测并判断最高可安装版本${FONT_COLOR_SUFFIX} ${YELLOW_FONT_PREFIX}h) 使用 apt 安装${FONT_COLOR_SUFFIX}"
local last_idx=$((${#versions_array[@]} - 1))
local default_idx=$last_idx
[[ $remembered_idx -ge 0 ]] && default_idx=$remembered_idx
local def_label="$default_idx"
[[ $remembered_idx -ge 0 ]] && def_label="${default_idx} (上次记忆)"
echo -e "${TIP} 输入 'a' 自动判断最高可安装版本,输入 'h' 使用 apt 安装:"
# 内核安装不可逆,超时一律取消而非自动执行;60 秒留足阅读上面版本列表的时间
if ! read -t 60 -rp "输入选项 [0-${last_idx} / a / h / q 取消,回车默认 ${def_label}]: " choice; then
echo ""
echo -e "${TIP} 等待超时,出于安全考虑已自动取消操作 (未做任何更改)。"
return 0
fi
# ---- q: 显式取消 ----
if [[ "$choice" =~ ^[qQ]$ ]]; then
echo -e "${INFO} 已取消操作。"
return 0
fi
# ---- a: 自动逐个尝试判断最高可安装版本 ----
if [[ "$choice" =~ ^[aA]$ ]]; then
# 先问是否需要 Headers:只装内核跑 BBR 不需要 Headers;要编译 LotSpeed/brutal 等模块才需要。
# 需要时探测会同时校验内核镜像 + 配套 Headers 依赖,只有两者都能干净安装的版本才会被选中,
# 从而自动避开像 +debN backports 这种"镜像能装、Headers 因 glibc/工具链过新装不了"的内核。
local need_headers=0 ans
echo -e "${INFO} 是否需要安装内核 Headers?(编译 LotSpeed / brutal 等内核模块才需要;只用 BBR 不需要)"
read -e -p "需要 Headers 请输 y,只装内核直接回车 [y/N]: " ans
[[ "$ans" =~ ^[yY]$ ]] && need_headers=1
if [[ $need_headers -eq 1 ]]; then
echo -e "${INFO} 开始自动探测:要求 内核镜像 + 配套 Headers 依赖同时可满足(从新到旧逐个校验,稍慢)..."
else
echo -e "${INFO} 开始自动探测最高可安装的 Cloud 内核版本(仅校验内核镜像依赖)..."
fi
detect_cloud_max "$img_url_base" "$need_headers" "$debarch" "${versions_array[@]}"
if [[ $CLOUD_MAX_IDX -lt 0 || -z "$CLOUD_MAX_FILE" ]]; then
echo -e "${TIP} 未找到可干净安装的 pool 版本,已回退到 apt 官方源安装 (最稳妥)。"
apt-get update >/dev/null 2>&1
if [[ $need_headers -eq 1 ]]; then
echo -e "${INFO} 正在使用 apt 安装 Cloud 内核及 Headers..."
apt-get install -y "linux-image-cloud-${debarch}" "linux-headers-cloud-${debarch}"
else
echo -e "${INFO} 正在使用 apt 安装 Cloud 内核..."
apt-get install -y "linux-image-cloud-${debarch}"
fi
BBR_grub
return 0
fi
# 记忆本次探测结果,下次进入默认选中
echo "$CLOUD_MAX_FILE" >"$CLOUD_STATE_FILE"
echo -e "${INFO} 探测完成!当前系统最高可安装版本: ${GREEN_FONT_PREFIX}${CLOUD_MAX_FILE}${FONT_COLOR_SUFFIX},开始安装..."
if install_kernel_generic "Debian 官方 Cloud" "" "${img_url_base}${CLOUD_MAX_FILE}"; then
[[ $need_headers -eq 1 ]] && ensure_cloud_headers "$CLOUD_MAX_FILE" "$debarch"
fi
return 0
fi