mirror of
http://bgp.hk.skcks.cn:10088/github.com/oneclickvirt/ecs
synced 2026-04-21 05:10:32 +08:00
Compare commits
659 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04725fef54 | ||
|
|
4e868a384a | ||
|
|
7a4885346b | ||
|
|
21b85b0176 | ||
|
|
8756224376 | ||
|
|
3a4b76ddcd | ||
|
|
a741e293b2 | ||
|
|
e3ca989aac | ||
|
|
0f06aa587d | ||
|
|
f7519a0307 | ||
|
|
6953182cb4 | ||
|
|
22c9eb7611 | ||
|
|
f4d6fe1a8a | ||
|
|
90f7b9a94b | ||
|
|
e5f8f87f7e | ||
|
|
0b87d169a3 | ||
|
|
b8fe9d3d98 | ||
|
|
8f46bd3f4f | ||
|
|
e8c4b2b4a7 | ||
|
|
8016a8fe93 | ||
|
|
bf0030dc49 | ||
|
|
6d02103aba | ||
|
|
5ffb48dcdc | ||
|
|
7c19502950 | ||
|
|
3c434781f5 | ||
|
|
f62636ca3e | ||
|
|
c4b11ae37d | ||
|
|
8f2fe236d5 | ||
|
|
347a0faa7a | ||
|
|
74640e3066 | ||
|
|
3b646eeeda | ||
|
|
eaad433395 | ||
|
|
03af7c423b | ||
|
|
0e96a6499b | ||
|
|
5b44f5f651 | ||
|
|
e4a759fceb | ||
|
|
e5c4b0ce8e | ||
|
|
188a1153e6 | ||
|
|
4c4887f487 | ||
|
|
feac73a427 | ||
|
|
f3048d074c | ||
|
|
58702b54e7 | ||
|
|
1e4d63ef57 | ||
|
|
9f3acacae0 | ||
|
|
e9755f0c20 | ||
|
|
d8b397b31b | ||
|
|
9e22d1bc23 | ||
|
|
284f6b8ba7 | ||
|
|
27203cf850 | ||
|
|
9b6922f55a | ||
|
|
447b25989e | ||
|
|
a50b68d184 | ||
|
|
6b68e643c7 | ||
|
|
14c79b9a89 | ||
|
|
60ae7ec3c3 | ||
|
|
3d78128f6a | ||
|
|
9c67a8d446 | ||
|
|
014dba0ce6 | ||
|
|
b5fdab4b27 | ||
|
|
16c4c2ff92 | ||
|
|
e1991c850f | ||
|
|
0b7c7865ff | ||
|
|
238ea3eb6f | ||
|
|
b859c52ba3 | ||
|
|
f1642843dd | ||
|
|
90ba076afb | ||
|
|
d31d20e16f | ||
|
|
a4084835f9 | ||
|
|
39c2607b42 | ||
|
|
519c0f3e86 | ||
|
|
3b5b8a348e | ||
|
|
858585b4ff | ||
|
|
b891416147 | ||
|
|
8e79568895 | ||
|
|
01aa051c96 | ||
|
|
628a380122 | ||
|
|
2b94d289f3 | ||
|
|
e3676760da | ||
|
|
55fbe111ac | ||
|
|
a37e6b1021 | ||
|
|
06b68ce205 | ||
|
|
9a839de71c | ||
|
|
09afd48ad5 | ||
|
|
cbc5bed3d4 | ||
|
|
834ff78bd9 | ||
|
|
351a4b552f | ||
|
|
d619991f9c | ||
|
|
b065af63d6 | ||
|
|
4d49222094 | ||
|
|
78b5634306 | ||
|
|
a0bc2de61a | ||
|
|
3c45667c55 | ||
|
|
6111554225 | ||
|
|
1bba9cde26 | ||
|
|
280d292c4c | ||
|
|
cf85b67ae4 | ||
|
|
3ebbf186fc | ||
|
|
3ba3301cc7 | ||
|
|
8674a82eb0 | ||
|
|
5116493338 | ||
|
|
c6f3e7a285 | ||
|
|
40f63e6cb1 | ||
|
|
6b42f626b0 | ||
|
|
4503fb55b0 | ||
|
|
cb1634cb68 | ||
|
|
95ece8c28d | ||
|
|
fd44c079b3 | ||
|
|
5c5bd37074 | ||
|
|
1676045e3d | ||
|
|
58e6941bfa | ||
|
|
3989708c4e | ||
|
|
93fd68bf82 | ||
|
|
793c44163a | ||
|
|
b403d71115 | ||
|
|
e4e11dd132 | ||
|
|
1c876f5199 | ||
|
|
fb9ae4d0e0 | ||
|
|
57e87348d4 | ||
|
|
d40bf5a195 | ||
|
|
753738931d | ||
|
|
a702c5416a | ||
|
|
a587d71348 | ||
|
|
110e6d34f9 | ||
|
|
4d18497dd3 | ||
|
|
c676cd83cb | ||
|
|
58e1de4487 | ||
|
|
89eecd2acc | ||
|
|
194dee49fd | ||
|
|
1e88513b8e | ||
|
|
f84023d18b | ||
|
|
2cfd5af3c0 | ||
|
|
ed66e2804a | ||
|
|
174bf303af | ||
|
|
b75f42ffe5 | ||
|
|
56b71ac53f | ||
|
|
1045d3fab8 | ||
|
|
7bd2b59d58 | ||
|
|
cc1da7ea7c | ||
|
|
1a002a1681 | ||
|
|
ee2b55e7eb | ||
|
|
a55cebf94b | ||
|
|
0571a8df13 | ||
|
|
f29a2829f3 | ||
|
|
33b8e0396f | ||
|
|
c259073d1b | ||
|
|
07ebc8cab5 | ||
|
|
1824051e53 | ||
|
|
9f93a2e59d | ||
|
|
8cd09182da | ||
|
|
9bb776d411 | ||
|
|
12f2da9da2 | ||
|
|
7aa70ac1fd | ||
|
|
04ce926582 | ||
|
|
73eb38eed1 | ||
|
|
9bc8a934b1 | ||
|
|
7b729e073b | ||
|
|
111126ae90 | ||
|
|
9397f789be | ||
|
|
5a1dda6483 | ||
|
|
e322c717c0 | ||
|
|
778b33142b | ||
|
|
aa9f361380 | ||
|
|
3236c60359 | ||
|
|
73b0f30ddc | ||
|
|
825da78bd5 | ||
|
|
5d2f3c7f96 | ||
|
|
61247a206e | ||
|
|
f0daad1360 | ||
|
|
2d23fb55a0 | ||
|
|
a73dbf2d0b | ||
|
|
b38dd713d9 | ||
|
|
e66ef1f106 | ||
|
|
f6ee1e40ec | ||
|
|
cb2bf0a7e5 | ||
|
|
ad017db5a6 | ||
|
|
a99f58518a | ||
|
|
2e59bac322 | ||
|
|
4132b1daff | ||
|
|
53296b745a | ||
|
|
74630e9615 | ||
|
|
5ec7924214 | ||
|
|
7a7fdc26a0 | ||
|
|
d4c855de92 | ||
|
|
7c22dee443 | ||
|
|
797496b640 | ||
|
|
5b686abdc8 | ||
|
|
f99a37edbe | ||
|
|
4ff49c8b90 | ||
|
|
1d9257beb3 | ||
|
|
fc6ccb9f92 | ||
|
|
88a2a7fdc9 | ||
|
|
5ff18ed7c7 | ||
|
|
df897db244 | ||
|
|
9a8680491c | ||
|
|
33f81fd6aa | ||
|
|
940703c3f9 | ||
|
|
1c2e9cdab9 | ||
|
|
3e6524fa0e | ||
|
|
026f40dc4c | ||
|
|
110c58d401 | ||
|
|
06e76a9c33 | ||
|
|
6b88a81c02 | ||
|
|
5482506bab | ||
|
|
b7130db8ce | ||
|
|
dc5e3b7852 | ||
|
|
6937e69a0a | ||
|
|
a68d33739c | ||
|
|
94e0441801 | ||
|
|
39be183fda | ||
|
|
dbc1506518 | ||
|
|
149f5673d2 | ||
|
|
c1b7302485 | ||
|
|
bf44ea9324 | ||
|
|
191ddfd668 | ||
|
|
89a99a7428 | ||
|
|
c474c71091 | ||
|
|
43b2c8aca3 | ||
|
|
96117a040e | ||
|
|
c5aeda45bd | ||
|
|
0b2ac51f09 | ||
|
|
ffe1b65a2b | ||
|
|
a4bfd4d143 | ||
|
|
edbcf1c245 | ||
|
|
4c65417ea6 | ||
|
|
2cf7484881 | ||
|
|
d5da2a59b6 | ||
|
|
372deb59eb | ||
|
|
8e4c6dfd3e | ||
|
|
8fc828d416 | ||
|
|
8a3fbd79e6 | ||
|
|
5628f1bb9c | ||
|
|
cadbb2a45c | ||
|
|
56d7560471 | ||
|
|
75e7eb1b25 | ||
|
|
24ba56cfa6 | ||
|
|
ebefd64a3d | ||
|
|
4d83ffea02 | ||
|
|
01a4084462 | ||
|
|
6674093425 | ||
|
|
6d2e56b1ec | ||
|
|
2a736d3e70 | ||
|
|
d02383b8cb | ||
|
|
0caba0ea60 | ||
|
|
6f92b8a994 | ||
|
|
cc34baf9e1 | ||
|
|
84d8963684 | ||
|
|
a94e9a6284 | ||
|
|
06007c191f | ||
|
|
d013b8f90c | ||
|
|
42002fdae1 | ||
|
|
d7628a5a57 | ||
|
|
e5f3ca1ec3 | ||
|
|
e70a295a5f | ||
|
|
58821f2603 | ||
|
|
33a656304b | ||
|
|
33d9a3ccb0 | ||
|
|
1d5758999c | ||
|
|
94ce394e04 | ||
|
|
ede04bd2c2 | ||
|
|
50f6ef1f60 | ||
|
|
e5129ab244 | ||
|
|
15b6ba4eb8 | ||
|
|
6c77f74003 | ||
|
|
61a1508b53 | ||
|
|
022284018d | ||
|
|
1d682213fe | ||
|
|
649a09b50a | ||
|
|
0fa2ff9300 | ||
|
|
b174e5cfa8 | ||
|
|
afd667db59 | ||
|
|
d846dcbf4f | ||
|
|
f47b1e3023 | ||
|
|
63658bb2dc | ||
|
|
b1a8368af3 | ||
|
|
df6d1236cc | ||
|
|
7c64102581 | ||
|
|
58cb4f3831 | ||
|
|
20bddae048 | ||
|
|
3c0590ca8d | ||
|
|
a752eeeeb0 | ||
|
|
630a28f2f2 | ||
|
|
cfd70e100b | ||
|
|
9114f5b97a | ||
|
|
8c0fc16384 | ||
|
|
2ded570639 | ||
|
|
42f0cb3399 | ||
|
|
14adbddeb9 | ||
|
|
27fd06b007 | ||
|
|
88db8df827 | ||
|
|
ec728796f4 | ||
|
|
147e8c1113 | ||
|
|
b9b1d5ca76 | ||
|
|
1500c8342e | ||
|
|
e523ca3c84 | ||
|
|
42943370bb | ||
|
|
7339f0336c | ||
|
|
54bbe16563 | ||
|
|
2a66452f40 | ||
|
|
aab6bf197d | ||
|
|
9206088bad | ||
|
|
48e150036d | ||
|
|
486b767a25 | ||
|
|
5a2e68bf92 | ||
|
|
97d05f4b57 | ||
|
|
4f08a33de8 | ||
|
|
ff8712a743 | ||
|
|
abd38554b6 | ||
|
|
918a9b3a46 | ||
|
|
d9dac50487 | ||
|
|
cd65f04433 | ||
|
|
8b5193eca1 | ||
|
|
4797ff0b34 | ||
|
|
9886cad73e | ||
|
|
ca0470f01a | ||
|
|
5a6d6845c1 | ||
|
|
487dd7c1d2 | ||
|
|
2dbf97de8c | ||
|
|
97e7cae2c2 | ||
|
|
d61a1879f5 | ||
|
|
29dd4ac57e | ||
|
|
dc3eff1fe3 | ||
|
|
0a0f2199bc | ||
|
|
91004d87f5 | ||
|
|
8f41c37203 | ||
|
|
12b1ae0702 | ||
|
|
653cd75a97 | ||
|
|
ea36e88c9f | ||
|
|
c81ebb3c7a | ||
|
|
7896b3ead5 | ||
|
|
eb98a7b857 | ||
|
|
d4d86229de | ||
|
|
651a183382 | ||
|
|
afc313a2a8 | ||
|
|
39ac8d198d | ||
|
|
a70dc2bab1 | ||
|
|
5041a16a9a | ||
|
|
21deb3587e | ||
|
|
3bac30edc2 | ||
|
|
9ef2ec4a9e | ||
|
|
ac33e00e0a | ||
|
|
7a439f7095 | ||
|
|
e3bfa65f66 | ||
|
|
74e33a212c | ||
|
|
bba8595033 | ||
|
|
530181be87 | ||
|
|
7deb986209 | ||
|
|
ea8e5efbd3 | ||
|
|
db88ee8479 | ||
|
|
67a3bfdaad | ||
|
|
e57ce05c0c | ||
|
|
62137f0bb1 | ||
|
|
f9d8c9ee4c | ||
|
|
913a1725c1 | ||
|
|
f1acbd361b | ||
|
|
d6f62f8624 | ||
|
|
94da890522 | ||
|
|
42b94260de | ||
|
|
16e2603176 | ||
|
|
0ed9840038 | ||
|
|
31b53e13b0 | ||
|
|
fb697fa25f | ||
|
|
2510b3ead0 | ||
|
|
b64b8a2d96 | ||
|
|
663614117d | ||
|
|
a96c9b6c7d | ||
|
|
e8f73ba4b8 | ||
|
|
dd6fbff943 | ||
|
|
e473851a02 | ||
|
|
86fc407ccb | ||
|
|
8e6037c340 | ||
|
|
be59af4411 | ||
|
|
7ae753a4e2 | ||
|
|
3956420ffe | ||
|
|
6ebe25af2d | ||
|
|
fe02bd295a | ||
|
|
172b520702 | ||
|
|
d460e2b167 | ||
|
|
03f5fd9e48 | ||
|
|
0b0972cc3e | ||
|
|
8273895def | ||
|
|
3e31671502 | ||
|
|
42b3ab3cff | ||
|
|
0bef8161e7 | ||
|
|
4f7e37f37c | ||
|
|
c0dcea53e3 | ||
|
|
02034da2d7 | ||
|
|
aa6c835aed | ||
|
|
2ebfdf7533 | ||
|
|
28e21f0838 | ||
|
|
23aae0b32b | ||
|
|
abaf021e1c | ||
|
|
be4e46ceba | ||
|
|
4746ae14cf | ||
|
|
eb702c4d97 | ||
|
|
86bde156a4 | ||
|
|
08be6238d2 | ||
|
|
c0472209b4 | ||
|
|
e751b76099 | ||
|
|
bf653ab620 | ||
|
|
c4b7ba00a1 | ||
|
|
87a288907a | ||
|
|
c3aa62753b | ||
|
|
83b8efd7ee | ||
|
|
2fa0270e4e | ||
|
|
3bd1136683 | ||
|
|
d5837ed535 | ||
|
|
080c62cd93 | ||
|
|
ce690aab2d | ||
|
|
05ec4d75b8 | ||
|
|
553e0ddef6 | ||
|
|
819d155fd5 | ||
|
|
d7519801b0 | ||
|
|
d6302be4ae | ||
|
|
a8366005dc | ||
|
|
4e3249b590 | ||
|
|
8a52f6df76 | ||
|
|
ebe85216bb | ||
|
|
7909d4ba78 | ||
|
|
4939d155d8 | ||
|
|
d69f85d0db | ||
|
|
f4dea6ce6e | ||
|
|
bc596e8e48 | ||
|
|
116ae33505 | ||
|
|
a32846638b | ||
|
|
d84fb21f82 | ||
|
|
f199336cf0 | ||
|
|
a1196ab8d2 | ||
|
|
9431fe3944 | ||
|
|
5dcef9f4c3 | ||
|
|
865ca62f06 | ||
|
|
eea78a7c30 | ||
|
|
2f5feace7b | ||
|
|
615ffca90f | ||
|
|
685fe735f6 | ||
|
|
5545990854 | ||
|
|
91d1aa4461 | ||
|
|
94f9576383 | ||
|
|
ef91a61d22 | ||
|
|
bafd77e2f5 | ||
|
|
eb03d50cd2 | ||
|
|
dbd921d8ca | ||
|
|
8b9df2c385 | ||
|
|
ce57c141a0 | ||
|
|
da3dc7e11e | ||
|
|
d4cbd0772a | ||
|
|
2ff558ae7b | ||
|
|
f5d9752e89 | ||
|
|
1f563c823a | ||
|
|
807141751a | ||
|
|
e55a18b3de | ||
|
|
920054c844 | ||
|
|
3c5405dd07 | ||
|
|
a33702f164 | ||
|
|
daf07945dc | ||
|
|
88b39c0ae3 | ||
|
|
65ddb32d74 | ||
|
|
acf2f0e98c | ||
|
|
e63dc4d3f4 | ||
|
|
7fb4e358e8 | ||
|
|
15ba7fb00f | ||
|
|
916f21a72f | ||
|
|
f00d5db69c | ||
|
|
6dbb171d3b | ||
|
|
0921aef041 | ||
|
|
dd4a4e317b | ||
|
|
d80f3451ff | ||
|
|
ebce9e493d | ||
|
|
176899d3e9 | ||
|
|
e58da9e921 | ||
|
|
651bbb94d0 | ||
|
|
91ffbfa417 | ||
|
|
84b0303468 | ||
|
|
f3e10a999b | ||
|
|
76a6e09d97 | ||
|
|
9c9dbc6200 | ||
|
|
20c8063e5e | ||
|
|
fbe5eb8e58 | ||
|
|
290f0d8d8c | ||
|
|
140347c2a6 | ||
|
|
aeda7a6e88 | ||
|
|
a205ca38be | ||
|
|
43bf9c81ed | ||
|
|
d47f8987a7 | ||
|
|
ffbe5a539a | ||
|
|
4b8ae06df8 | ||
|
|
859e783241 | ||
|
|
3cd912219e | ||
|
|
a85a06b440 | ||
|
|
681da08f01 | ||
|
|
bdd00d46f8 | ||
|
|
1f213bb788 | ||
|
|
1dec7c323b | ||
|
|
391cc351c4 | ||
|
|
282a947156 | ||
|
|
73916d324a | ||
|
|
6cce2a7e8e | ||
|
|
2a653930a8 | ||
|
|
2295035706 | ||
|
|
3e8c7112a0 | ||
|
|
a8175219d5 | ||
|
|
a827583b76 | ||
|
|
82a1d19c6c | ||
|
|
4ef0ac8d5a | ||
|
|
3bb70191e7 | ||
|
|
1bca0052c5 | ||
|
|
7c06d278e0 | ||
|
|
5cec395f3c | ||
|
|
4994994d4f | ||
|
|
91ad962b13 | ||
|
|
8356d95566 | ||
|
|
1428087818 | ||
|
|
7b1e78fb7e | ||
|
|
0a62c62dd8 | ||
|
|
34b4f66289 | ||
|
|
eca268048f | ||
|
|
c3ffe741ad | ||
|
|
9de837ef71 | ||
|
|
2c3da79871 | ||
|
|
e7ad87ecbd | ||
|
|
a89a79c11c | ||
|
|
ee38b65a5a | ||
|
|
0a756f04ca | ||
|
|
f040ffb1d4 | ||
|
|
efb870a9de | ||
|
|
9151cd3b70 | ||
|
|
ef8e2789e2 | ||
|
|
2dad691bd6 | ||
|
|
a9c5ed359a | ||
|
|
e3c88308c9 | ||
|
|
617c6c2f98 | ||
|
|
d42d6999a2 | ||
|
|
4c0f54181c | ||
|
|
f2828970f5 | ||
|
|
7d84d55823 | ||
|
|
daad5158d4 | ||
|
|
859eea7b69 | ||
|
|
50b172d274 | ||
|
|
1534cc19b0 | ||
|
|
f0e5af9dcd | ||
|
|
900083ad25 | ||
|
|
5784e9b76f | ||
|
|
a79e908f40 | ||
|
|
53c25a6230 | ||
|
|
aa76fe134c | ||
|
|
a1491585d6 | ||
|
|
b5fce2e16b | ||
|
|
ac1da2af0a | ||
|
|
6ff440a9e9 | ||
|
|
1a3049e49d | ||
|
|
b463cd3085 | ||
|
|
bf16dfa387 | ||
|
|
e00415960f | ||
|
|
f0ed7c5b1c | ||
|
|
e6500a23ee | ||
|
|
b1d0611f6b | ||
|
|
b78cf8a1ac | ||
|
|
34065b0161 | ||
|
|
f2e4ca6a0b | ||
|
|
a8c76a2ee2 | ||
|
|
d6319e5a02 | ||
|
|
2213e52335 | ||
|
|
0f5b11cc5f | ||
|
|
36fda2e287 | ||
|
|
7dfece6e29 | ||
|
|
ffb9bfdf7f | ||
|
|
17e96a94fe | ||
|
|
b16b22c117 | ||
|
|
9a6879207f | ||
|
|
8e66e5c801 | ||
|
|
47d84ac3dd | ||
|
|
073a894e26 | ||
|
|
7a92e6688e | ||
|
|
971a2c66c7 | ||
|
|
b4e9e17925 | ||
|
|
5de0e9734e | ||
|
|
465c6031f8 | ||
|
|
a421e12435 | ||
|
|
766f978e3e | ||
|
|
ae9d74c17c | ||
|
|
958c0c4153 | ||
|
|
9924bb1e72 | ||
|
|
a6d28bd72a | ||
|
|
955b887039 | ||
|
|
ac3839a8eb | ||
|
|
4d2c89dc96 | ||
|
|
e773e4d0fd | ||
|
|
d0814ee1da | ||
|
|
c31c978fc8 | ||
|
|
040a3db6e2 | ||
|
|
cd68643df8 | ||
|
|
5ad59dcd36 | ||
|
|
476ae5d25f | ||
|
|
369ae42139 | ||
|
|
676973d946 | ||
|
|
3ce8b69065 | ||
|
|
d752a2fb7f | ||
|
|
95fba10d0f | ||
|
|
acebe04732 | ||
|
|
226f751655 | ||
|
|
18a0de4c4e | ||
|
|
e5f29dc46c | ||
|
|
c94d0133fc | ||
|
|
7cd1e9190b | ||
|
|
df735880fe | ||
|
|
f0b89d4168 | ||
|
|
8af46d4bfb | ||
|
|
e8fdbee3e4 | ||
|
|
6cbd20fc90 | ||
|
|
25c0b2ba95 | ||
|
|
f16c0e7bcf | ||
|
|
12e29bc105 | ||
|
|
f7a7e34b34 | ||
|
|
4d7049c9b8 | ||
|
|
ad61caf1d2 | ||
|
|
f84cbe2078 | ||
|
|
a9df37f216 | ||
|
|
60bec3d46c | ||
|
|
3c1e1f19c4 | ||
|
|
207d8ff746 | ||
|
|
835dc7a0fd | ||
|
|
cdfb956211 | ||
|
|
d5ba31e5d7 | ||
|
|
e80ec4f063 | ||
|
|
cdb8d8681e | ||
|
|
d6d0abeb1c | ||
|
|
9565248f95 | ||
|
|
1d6cd5984b | ||
|
|
63b857c915 | ||
|
|
6811f19db4 | ||
|
|
847e1c0d6e | ||
|
|
99a8448d53 | ||
|
|
4fe9cd21ef | ||
|
|
bdb53ac436 | ||
|
|
01b9ee4505 | ||
|
|
fadbf9c6aa | ||
|
|
60e235e4d7 | ||
|
|
da4b431761 | ||
|
|
7fff30521e | ||
|
|
e7c78774ca | ||
|
|
fb6f9da14c | ||
|
|
7b1773c5f0 | ||
|
|
7b15a74448 | ||
|
|
956f65941e | ||
|
|
6d1f806e94 | ||
|
|
d727d6b778 | ||
|
|
ea891861b3 | ||
|
|
e0826a80c8 | ||
|
|
74868a918e | ||
|
|
889ecee3f3 | ||
|
|
f6dd0bbb56 | ||
|
|
01686f3e9d | ||
|
|
5a6cd7d671 | ||
|
|
35e183b640 | ||
|
|
a684052966 | ||
|
|
2beb74c319 | ||
|
|
5218c95162 | ||
|
|
83be2066c8 | ||
|
|
975bcbfc00 | ||
|
|
dbddbc29a3 |
310
.back/create_public_branch.py
Executable file
310
.back/create_public_branch.py
Executable file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to create public branch by removing security dependencies and references.
|
||||
This script properly handles Go file modifications to ensure the code can compile.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
|
||||
def read_file(filepath):
|
||||
"""Read file content."""
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def write_file(filepath, content):
|
||||
"""Write content to file."""
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
def modify_go_mod(filepath):
|
||||
"""
|
||||
Modify go.mod to remove privatespeedtest (and optional security) dependencies.
|
||||
Automatically matches module names regardless of version or indirect comment.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Modules to remove
|
||||
remove_modules = [
|
||||
r'github\.com/oneclickvirt/privatespeedtest',
|
||||
r'github\.com/oneclickvirt/security',
|
||||
]
|
||||
|
||||
for mod in remove_modules:
|
||||
# Remove full require line (with or without // indirect)
|
||||
content = re.sub(
|
||||
rf'^[ \t]*{mod}[ \t]+v[^\s]+(?:[ \t]+// indirect)?[ \t]*\n',
|
||||
'',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Removed privatespeedtest/security from {filepath}")
|
||||
|
||||
|
||||
def remove_code_block(lines, start_marker, end_condition='empty_line'):
|
||||
"""
|
||||
Remove code block from lines starting with start_marker.
|
||||
|
||||
Args:
|
||||
lines: List of file lines
|
||||
start_marker: String or list of strings to identify block start
|
||||
end_condition: 'empty_line' (default) or 'closing_brace' or custom function
|
||||
|
||||
Returns:
|
||||
Modified lines with the block removed
|
||||
"""
|
||||
if isinstance(start_marker, str):
|
||||
start_marker = [start_marker]
|
||||
|
||||
result = []
|
||||
skip_mode = False
|
||||
brace_depth = 0
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Check if we should start skipping
|
||||
if not skip_mode:
|
||||
for marker in start_marker:
|
||||
if marker in line:
|
||||
skip_mode = True
|
||||
if end_condition == 'closing_brace':
|
||||
# Count opening braces on the function declaration line
|
||||
brace_depth = line.count('{') - line.count('}')
|
||||
break
|
||||
|
||||
if not skip_mode:
|
||||
result.append(line)
|
||||
else:
|
||||
# We're in skip mode
|
||||
if end_condition == 'empty_line':
|
||||
# Skip until we find an empty line
|
||||
if line.strip() == '':
|
||||
skip_mode = False
|
||||
# Don't add the empty line, continue to next
|
||||
elif end_condition == 'closing_brace':
|
||||
# Track brace depth
|
||||
brace_depth += line.count('{') - line.count('}')
|
||||
if brace_depth == 0 and '}' in line:
|
||||
# Function ended, skip until next empty line
|
||||
end_condition = 'empty_line'
|
||||
|
||||
i += 1
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def modify_speed_go(filepath):
|
||||
"""
|
||||
Remove privatespeedtest-related code from speed.go.
|
||||
Uses line-by-line processing for reliability.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
lines = content.split('\n')
|
||||
|
||||
# Remove specific code blocks by their comment markers
|
||||
blocks_to_remove = [
|
||||
'// formatString 格式化字符串到指定宽度',
|
||||
'// printTableRow 打印表格行',
|
||||
'// privateSpeedTest 使用 privatespeedtest 进行单个运营商测速',
|
||||
'// privateSpeedTestWithFallback 使用私有测速,如果失败则回退到 global 节点',
|
||||
'// 对于三网测速(cmcc、cu、ct)和 other,优先使用 privatespeedtest 进行私有测速',
|
||||
]
|
||||
|
||||
for block_marker in blocks_to_remove:
|
||||
lines = remove_code_block(lines, block_marker)
|
||||
|
||||
# Reconstruct content
|
||||
content = '\n'.join(lines)
|
||||
|
||||
# Remove privatespeedtest import
|
||||
content = re.sub(
|
||||
r'\n\s*"github\.com/oneclickvirt/privatespeedtest/pst"\s*\n',
|
||||
'\n',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Remove time import (only used by privatespeedtest)
|
||||
content = re.sub(
|
||||
r'\n\s*"time"\s*\n',
|
||||
'\n',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Clean up multiple consecutive empty lines (optional)
|
||||
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Removed privatespeedtest from {filepath}")
|
||||
|
||||
def modify_utils_go(filepath):
|
||||
"""
|
||||
Modify utils/utils.go to:
|
||||
1. Replace security/network import with basics/network
|
||||
2. Replace SecurityUploadToken usage with hardcoded token
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Replace import
|
||||
content = re.sub(
|
||||
r'"github\.com/oneclickvirt/security/network"',
|
||||
r'"github.com/oneclickvirt/basics/network"',
|
||||
content
|
||||
)
|
||||
|
||||
# Replace token usage - find the exact line and replace it
|
||||
content = re.sub(
|
||||
r'\ttoken := network\.SecurityUploadToken',
|
||||
r'\ttoken := "OvwKx5qgJtf7PZgCKbtyojSU.MTcwMTUxNzY1MTgwMw"',
|
||||
content
|
||||
)
|
||||
|
||||
# Update title for public version
|
||||
content = re.sub(
|
||||
r'VPS融合怪测试',
|
||||
r'VPS融合怪测试(非官方编译)',
|
||||
content
|
||||
)
|
||||
content = re.sub(
|
||||
r'VPS Fusion Monster Test',
|
||||
r'VPS Fusion Monster Test (Unofficial)',
|
||||
content
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
|
||||
|
||||
def modify_params_go(filepath):
|
||||
"""
|
||||
Modify internal/params/params.go to change security flag default to false.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Change default value in struct initialization
|
||||
content = re.sub(
|
||||
r'(\s+SecurityTestStatus:\s+)true,',
|
||||
r'\1false,',
|
||||
content
|
||||
)
|
||||
|
||||
# Change flag default value
|
||||
content = re.sub(
|
||||
r'(c\.GoecsFlag\.BoolVar\(&c\.SecurityTestStatus, "security", )true(, "Enable/Disable security test"\))',
|
||||
r'\1false\2',
|
||||
content
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
|
||||
def modify_readme(filepath, is_english=False):
|
||||
"""
|
||||
Modify README files to update Go version and security status.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Extract Go version from go.mod
|
||||
go_mod_content = read_file('go.mod')
|
||||
go_version_match = re.search(r'^go (\d+\.\d+(?:\.\d+)?)', go_mod_content, re.MULTILINE)
|
||||
|
||||
if not go_version_match:
|
||||
print(f"⚠ Warning: Could not extract Go version from go.mod")
|
||||
return
|
||||
|
||||
go_version = go_version_match.group(1)
|
||||
|
||||
if is_english:
|
||||
# Update Go version in English README
|
||||
content = re.sub(
|
||||
r'Select go \d+\.\d+\.\d+ version to install',
|
||||
f'Select go {go_version} version to install',
|
||||
content
|
||||
)
|
||||
|
||||
# Update security status
|
||||
content = re.sub(
|
||||
r', binary files compiled in \[securityCheck\][^\)]*\)',
|
||||
', but open sourced',
|
||||
content
|
||||
)
|
||||
|
||||
# Update help text for security flag
|
||||
content = re.sub(
|
||||
r'security\s+Enable/Disable security test \(default true\)',
|
||||
'security Enable/Disable security test (default false)',
|
||||
content
|
||||
)
|
||||
else:
|
||||
# Update Go version in Chinese README
|
||||
content = re.sub(
|
||||
r'选择 go \d+\.\d+\.\d+ 的版本进行安装',
|
||||
f'选择 go {go_version} 的版本进行安装',
|
||||
content
|
||||
)
|
||||
|
||||
# Update security status
|
||||
content = re.sub(
|
||||
r'二进制文件编译至 \[securityCheck\][^\)]*\)',
|
||||
'但已开源',
|
||||
content
|
||||
)
|
||||
|
||||
# Update help text for security flag
|
||||
content = re.sub(
|
||||
r'security\s+Enable/Disable security test \(default true\)',
|
||||
'security Enable/Disable security test (default false)',
|
||||
content
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to process all files."""
|
||||
print("Starting public branch creation process...")
|
||||
print()
|
||||
|
||||
# Check if we're in the right directory
|
||||
if not os.path.exists('go.mod'):
|
||||
print("Error: go.mod not found. Please run this script from the project root.")
|
||||
sys.exit(1)
|
||||
|
||||
# Modify Go source files
|
||||
print("Modifying Go source files...")
|
||||
modify_speed_go('internal/tests/speed.go')
|
||||
modify_utils_go('utils/utils.go')
|
||||
modify_params_go('internal/params/params.go')
|
||||
print()
|
||||
|
||||
# Modify go.mod
|
||||
print("Modifying go.mod...")
|
||||
modify_go_mod('go.mod')
|
||||
print()
|
||||
|
||||
# Modify README files
|
||||
print("Modifying README files...")
|
||||
modify_readme('README_ZH.md', is_english=False)
|
||||
modify_readme('README.md', is_english=True)
|
||||
print()
|
||||
|
||||
print("✓ All modifications completed successfully!")
|
||||
print()
|
||||
print("Next steps:")
|
||||
print("1. Run 'go mod tidy' to clean up dependencies")
|
||||
print("2. Run 'go build -o maintest' to verify compilation")
|
||||
print("3. Test the binary with: ./maintest -menu=false -l en -security=false -upload=false")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
99
.github/workflows/build_binary.yaml
vendored
Normal file
99
.github/workflows/build_binary.yaml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Build and Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
# 1.20 是 Windows 7/8 Server 2008/2012 最后一个支持版本
|
||||
image: goreleaser/goreleaser-cross:v1.20
|
||||
steps:
|
||||
- run: |
|
||||
git config --global --add safe.directory /__w/ecs/ecs
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.25.4
|
||||
|
||||
# - name: Install UPX
|
||||
# run: |
|
||||
# apk add --no-cache upx
|
||||
|
||||
- name: Configure Git for Private Modules
|
||||
run: |
|
||||
git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global url."git@github.com:".insteadOf "https://github.com/"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
- name: Clean Go cache before build
|
||||
run: |
|
||||
go clean -cache -modcache -testcache
|
||||
df -h
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
# version: latest
|
||||
version: '~> v2'
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest
|
||||
|
||||
- name: Update goecs.sh with new version
|
||||
run: |
|
||||
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
else
|
||||
VERSION=$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.1.37")
|
||||
fi
|
||||
echo "Using version: $VERSION"
|
||||
FILE="goecs.sh"
|
||||
BRANCH="master"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global --unset url."git@github.com:".insteadOf || true
|
||||
git fetch origin $BRANCH
|
||||
git checkout $BRANCH
|
||||
if [ ! -f "$FILE" ]; then
|
||||
echo "Error: $FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
sed -i "s/\(_yellow \"Unable to get version info, using default version \).*\(\".*\)/\1$VERSION\2/" "$FILE"
|
||||
sed -i "s/\(ECS_VERSION=\"\).*\(\"\)/\1$VERSION\2/" "$FILE"
|
||||
if git diff --quiet "$FILE"; then
|
||||
echo "No changes detected in $FILE"
|
||||
exit 0
|
||||
fi
|
||||
git add "$FILE"
|
||||
git commit -m "chore: update ECS_VERSION to $VERSION in goecs.sh"
|
||||
git push origin $BRANCH
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
61
.github/workflows/build_docker.yaml
vendored
Normal file
61
.github/workflows/build_docker.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build and Push Docker Image
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build and Release"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
# - name: Login to CNB Registry
|
||||
# uses: docker/login-action@v2
|
||||
# with:
|
||||
# registry: ${{ secrets.CNB_DOCKER_REGISTRY }}
|
||||
# username: ${{ secrets.CNB_USERNAME }}
|
||||
# password: ${{ secrets.CNB_TOKEN }}
|
||||
|
||||
- name: Login to Aliyun Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com
|
||||
username: ${{ secrets.ALIYUN_USERNAME }}
|
||||
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/riscv64
|
||||
# linux/mips,linux/mipsle 暂不支持 alpine, linux/s390x 编译卡死,cnb组织空间不足无法推送
|
||||
# ${{ secrets.CNB_DOCKER_REGISTRY }}/oneclickvirt/ecs:latest
|
||||
push: true
|
||||
tags: |
|
||||
${{ secrets.DOCKER_USERNAME }}/goecs:latest
|
||||
crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com/oneclickvirt/ecs:latest
|
||||
ghcr.io/${{ github.repository_owner }}/goecs:latest
|
||||
84
.github/workflows/build_public.yml
vendored
Normal file
84
.github/workflows/build_public.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
name: Public Build
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build and Release"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Update master branch README files
|
||||
run: |
|
||||
git config --global user.name 'GitHub Actions'
|
||||
git config --global user.email 'actions@github.com'
|
||||
|
||||
if [ -f "go.mod" ]; then
|
||||
GO_VERSION=$(grep "^go " go.mod | head -n 1 | awk '{print $2}')
|
||||
echo "提取到的 Go 版本: $GO_VERSION"
|
||||
if [ -n "$GO_VERSION" ] && [[ "$GO_VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
||||
echo "版本验证成功,开始替换..."
|
||||
if [ -f "README.md" ]; then
|
||||
sed -i "s/选择 go [0-9]\+\.[0-9]\+\.[0-9]\+ 的版本进行安装/选择 go $GO_VERSION 的版本进行安装/g" README.md
|
||||
sed -i 's|但二进制文件编译至 \[securityCheck\].*)|但已开源|g' README.md
|
||||
sed -i 's|security.*Enable/Disable security test (default true)|security Enable/Disable security test (default false)|g' README.md
|
||||
echo "已更新 README.md"
|
||||
fi
|
||||
if [ -f "README_EN.md" ]; then
|
||||
sed -i "s/Select go [0-9]\+\.[0-9]\+\.[0-9]\+ version to install/Select go $GO_VERSION version to install/g" README_EN.md
|
||||
sed -i 's|but binary files compiled in \[securityCheck\].*)|but open sourced|g' README_EN.md
|
||||
sed -i 's|security.*Enable/Disable security test (default true)|security Enable/Disable security test (default false)|g' README_EN.md
|
||||
echo "已更新 README_EN.md"
|
||||
fi
|
||||
git add README.md
|
||||
[ -f "README_EN.md" ] && git add README_EN.md || true
|
||||
git commit -m "Auto update README files" || echo "No changes to commit"
|
||||
git push origin ${{ github.ref_name }}
|
||||
else
|
||||
echo "错误:未能提取到有效的 Go 版本号或版本号格式不正确"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "错误:未找到 go.mod 文件"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create public branch
|
||||
run: |
|
||||
# 删除本地 public 分支(如果存在)
|
||||
git branch -D public 2>/dev/null || true
|
||||
# 基于当前分支创建新的 public 分支(完全覆盖)
|
||||
git checkout -b public
|
||||
|
||||
- name: Remove security package references
|
||||
run: |
|
||||
python3 .back/create_public_branch.py
|
||||
rm -f go.sum
|
||||
go clean -modcache
|
||||
go clean -cache -testcache -fuzzcache
|
||||
go mod tidy
|
||||
- name: Update Go version in README files
|
||||
run: |
|
||||
# This step is now handled by the Python script
|
||||
echo "README files already updated by create_public_branch.py"
|
||||
- name: Build and Test
|
||||
run: |
|
||||
go build -o maintest
|
||||
./maintest -menu=false -l en -security=false -upload=false || exit 1
|
||||
rm -rf maintest
|
||||
|
||||
- name: Commit and push changes
|
||||
run: |
|
||||
git add .
|
||||
git commit -m "Auto update public version (no security package)" || echo "No changes to commit"
|
||||
git push -f origin public
|
||||
66
.github/workflows/ci.yaml
vendored
66
.github/workflows/ci.yaml
vendored
@@ -1,66 +0,0 @@
|
||||
name: go-ci
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: set up
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.22.4
|
||||
id: go
|
||||
- name: check out
|
||||
uses: actions/checkout@v3
|
||||
- name: Cache
|
||||
uses: actions/cache@v2.1.0
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Configure Git for Private Modules
|
||||
run: |
|
||||
git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global url."git@github.com:".insteadOf "https://github.com/"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
env:
|
||||
GOPRIVATE: github.com/oneclickvirt/security
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
build:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Configure Git for Private Modules
|
||||
run: |
|
||||
git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global url."git@github.com:".insteadOf "https://github.com/"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
- name: build
|
||||
run: go build ./...
|
||||
env:
|
||||
GOPRIVATE: github.com/oneclickvirt/security
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
# test:
|
||||
# needs: setup
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - name: Configure Git for Private Modules
|
||||
# run: |
|
||||
# git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
# git config --global url."git@github.com:".insteadOf "https://github.com/"
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
# - name: test
|
||||
# run: go test ./goecs_test.go
|
||||
# env:
|
||||
# GOPRIVATE: github.com/oneclickvirt/security
|
||||
# GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
38
.github/workflows/main.yaml
vendored
38
.github/workflows/main.yaml
vendored
@@ -1,38 +0,0 @@
|
||||
name: goreleaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.22.4
|
||||
|
||||
- name: Configure Git for Private Modules
|
||||
run: |
|
||||
git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
git config --global url."git@github.com:".insteadOf "https://github.com/"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
GOPRIVATE: github.com/oneclickvirt/security
|
||||
CGO_ENABLED: 0 # 禁用 CGO,强制静态链接
|
||||
59
.github/workflows/sync.yaml
vendored
Normal file
59
.github/workflows/sync.yaml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: Sync Latest Release
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Build and Release"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
sync-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest release
|
||||
id: get_release
|
||||
run: |
|
||||
echo "RELEASE_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
|
||||
|
||||
- name: Create temporary directory
|
||||
run: |
|
||||
mkdir -p temp_repo
|
||||
cd temp_repo
|
||||
git init
|
||||
git config --local user.name "GitHub Action"
|
||||
git config --local user.email "action@github.com"
|
||||
|
||||
- name: Copy repository files
|
||||
run: |
|
||||
cp goecs.sh temp_repo/
|
||||
cp README.md temp_repo/
|
||||
[ -f "README_EN.md" ] && cp README_EN.md temp_repo/ || true
|
||||
[ -f "README_ZH.md" ] && cp README_ZH.md temp_repo/ || true
|
||||
cp LICENSE temp_repo/
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
cd temp_repo
|
||||
gh release download ${{ env.RELEASE_TAG }} --repo ${{ github.repository }} --dir .
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push to target repository
|
||||
run: |
|
||||
cd temp_repo
|
||||
git add .
|
||||
git commit -m "Sync release ${{ env.RELEASE_TAG }} from ${{ github.repository }}"
|
||||
git branch -M main
|
||||
git remote add target https://cnb.cool/oneclickvirt/ecs.git
|
||||
echo "machine cnb.cool login ${{ secrets.CNB_USERNAME }} password ${{ secrets.CNB_TOKEN }}" > ~/.netrc
|
||||
chmod 600 ~/.netrc
|
||||
git push -f target main
|
||||
env:
|
||||
CNB_USERNAME: ${{ secrets.CNB_USERNAME }}
|
||||
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
|
||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
vendor/
|
||||
.idea/
|
||||
ecs
|
||||
goecs.txt
|
||||
*.log
|
||||
107
.goreleaser.yaml
Normal file
107
.goreleaser.yaml
Normal file
@@ -0,0 +1,107 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -v
|
||||
- go clean -cache
|
||||
|
||||
project_name: goecs
|
||||
|
||||
builds:
|
||||
- id: universal
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
- amd64
|
||||
- mips
|
||||
- mipsle
|
||||
- mips64
|
||||
- mips64le
|
||||
- ppc64
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
gomips:
|
||||
- softfloat
|
||||
ignore:
|
||||
- goos: windows
|
||||
goarch: arm
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=o64-clang
|
||||
- CXX=o64-clang++
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=oa64-clang
|
||||
- CXX=oa64-clang++
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
universal_binaries:
|
||||
- name_template: "goecs"
|
||||
replace: false
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "goecs"
|
||||
|
||||
archives:
|
||||
- name_template: "goecs_{{ .Os }}_{{ .Arch }}"
|
||||
format: zip
|
||||
files:
|
||||
- none*
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^chore"
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
- go mod tidy
|
||||
- New translations
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
brute: true
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm64
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
9
.idea/ecs.iml
generated
9
.idea/ecs.iml
generated
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/ecs.iml" filepath="$PROJECT_DIR$/.idea/ecs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/sshConfigs.xml
generated
8
.idea/sshConfigs.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SshConfigs">
|
||||
<configs>
|
||||
<sshConfig authType="PASSWORD" host="49.234.158.14" id="950b8219-3ab3-47e7-ad76-6b6a5d0857b0" port="22" nameFormat="DESCRIPTIVE" username="root" useOpenSSHConfig="true" />
|
||||
</configs>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk update && apk add --no-cache wget curl bash || \
|
||||
(echo "Standard repo failed, trying edge repo..." && \
|
||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main wget curl bash)
|
||||
|
||||
RUN apk add --no-cache bind-tools || \
|
||||
(echo "Standard repo failed for bind-tools, trying edge repo..." && \
|
||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main bind-tools)
|
||||
|
||||
RUN apk add --no-cache grep openssl ca-certificates || \
|
||||
(echo "Standard repo failed, trying edge repo..." && \
|
||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main grep openssl ca-certificates)
|
||||
|
||||
RUN apk add --no-cache uuidgen || \
|
||||
apk add --no-cache util-linux || \
|
||||
(echo "Standard repo failed for uuidgen, trying edge repo..." && \
|
||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main uuidgen) || \
|
||||
apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main util-linux
|
||||
|
||||
RUN export noninteractive=true
|
||||
# 下载并执行 goecs.sh 脚本
|
||||
RUN curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && \
|
||||
chmod +x goecs.sh && \
|
||||
bash goecs.sh env && \
|
||||
bash goecs.sh install
|
||||
# 设置 goecs 为入口点
|
||||
ENTRYPOINT ["goecs"]
|
||||
687
LICENSE
687
LICENSE
@@ -1,21 +1,674 @@
|
||||
MIT License
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (c) 2024 oneclickvirt
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Preamble
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
449
README.md
449
README.md
@@ -1,30 +1,437 @@
|
||||
# ecs
|
||||
# ECS
|
||||
|
||||
[](https://github.com/oneclickvirt/ecs/actions/workflows/main.yaml)
|
||||
[](https://github.com/oneclickvirt/ecs/actions/workflows/build_binary.yaml)
|
||||
|
||||
融合怪测评脚本 - GO重构版本
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Foneclickvirt%2Fecs?ref=badge_shield)
|
||||
|
||||
Shell版本: https://github.com/spiritLHLS/ecs
|
||||
[](https://hits.spiritlhl.net) [](https://github.com/oneclickvirt/ecs/releases)
|
||||
|
||||
## 功能
|
||||
Fusion Monster Evaluation Project - GO Version
|
||||
|
||||
- [x] 系统基础信息查询[自研[basics](https://github.com/oneclickvirt/basics)、[gostun](https://github.com/oneclickvirt/gostun)]
|
||||
- [x] IP基础信息并发查询[自研[basics](https://github.com/oneclickvirt/basics)]
|
||||
- [x] CPU测试[自研[cputest](https://github.com/oneclickvirt/cputest)支持sysbench、geekbench、winsat]
|
||||
- [x] 内存测试[自研[memorytest](https://github.com/oneclickvirt/memorytest)支持sysbench、dd]
|
||||
- [x] 硬盘测试[自研[disktest](https://github.com/oneclickvirt/disktest)支持dd、fio、winsat]
|
||||
- [x] 御三家流媒体解锁信息并发查询[借鉴[netflix-verify](https://github.com/sjlleo/netflix-verify)、[VerifyDisneyPlus](https://github.com/sjlleo/VerifyDisneyPlus)、[TubeCheck](https://github.com/sjlleo/TubeCheck)二次开发至于[CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests)]
|
||||
- [x] 常见流媒体测试并发查询[自研代码,逻辑借鉴[RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck)、[MediaUnlockTest](https://github.com/HsukqiLee/MediaUnlockTest)并自行修复错漏至于[UnlockTests](https://github.com/oneclickvirt/UnlockTests)]
|
||||
- [x] IP质量/安全信息并发查询[自研,由于测试含密钥信息,故而私有化开发,但二进制文件编译至于[securityCheck](https://github.com/oneclickvirt/securityCheck)]
|
||||
- [x] 邮件端口测试[自研[portchecker](https://github.com/oneclickvirt/portchecker)]
|
||||
- [x] 三网回程测试[借鉴[zhanghanyun/backtrace](https://github.com/zhanghanyun/backtrace)二次开发至于[oneclickvirt/backtrace](https://github.com/oneclickvirt/backtrace)]
|
||||
- [x] 三网路由测试[借鉴[NTrace-core](https://github.com/nxtrace/NTrace-core)二次开发至于[nt3](https://github.com/oneclickvirt/nt3)]
|
||||
- [x] 测试网速[基于[speedtest.net-爬虫](https://github.com/spiritLHLS/speedtest.net-CN-ID)、[speedtest.cn-爬虫](https://github.com/spiritLHLS/speedtest.cn-CN-ID)的数据,借鉴[speedtest-go](https://github.com/showwin/speedtest-go)二次开发至于[oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest)]
|
||||
(No additional shell file dependencies unless necessary to install the environment using the shell, the environment is installed just to measure more accurately, in extreme cases no environment dependencies can also be fully measured project)
|
||||
|
||||
## TODO
|
||||
Please report any issues via [issues](https://github.com/oneclickvirt/ecs/issues).
|
||||
|
||||
- [ ] 测试三网Ping值[借鉴[ecsspeed](https://github.com/spiritLHLS/ecsspeed)的逻辑二次开发]
|
||||
Go version: [https://github.com/oneclickvirt/ecs](https://github.com/oneclickvirt/ecs)
|
||||
|
||||
## 说明
|
||||
Shell version: [https://github.com/spiritLHLS/ecs/blob/main/README_EN.md](https://github.com/spiritLHLS/ecs/blob/main/README_EN.md)
|
||||
|
||||
开发中,勿要使用
|
||||
---
|
||||
|
||||
## **Language**
|
||||
|
||||
[English Docs](README.md) | [中文文档](README_ZH.md)
|
||||
|
||||
---
|
||||
|
||||
## **Supported Systems and Architectures**
|
||||
|
||||
### **Compilation and Testing Support**
|
||||
| Supported for Compilation | Tested on | Supported OS for Compilation | Tested OS |
|
||||
|---------------------------|-----------|------------------------------|-----------|
|
||||
| amd64 | amd64 | Linux | Linux |
|
||||
| arm64 | arm64 | Windows | Windows |
|
||||
| arm | | MacOS(Darwin) | MacOS |
|
||||
| 386 | | FreeBSD | |
|
||||
| mips,mipsle | | Android | |
|
||||
| mips64,mips64le | | | |
|
||||
| ppc64,ppc64le | | | |
|
||||
| s390x | s390x | | |
|
||||
| riscv64 | | | |
|
||||
|
||||
> For more information about the architecture and system, please test or compile it yourself, and open issues if you have any questions.
|
||||
|
||||
### **Systems Pending Support**
|
||||
|
||||
| OS | Notes |
|
||||
|--------|-------------------------------------------------------------------------------------------------|
|
||||
| OpenBSD/NetBSD | Some of Golang's official libraries do not support this system (especially net-related items) |
|
||||
|
||||
---
|
||||
|
||||
## **Features**
|
||||
|
||||
- System basic information query and concurrent IP basic information query: Self-developed [basics](https://github.com/oneclickvirt/basics), [gostun](https://github.com/oneclickvirt/gostun)
|
||||
- CPU test: Self-developed [cputest](https://github.com/oneclickvirt/cputest) supporting sysbench(lua/golang version), geekbench, winsat
|
||||
- Memory test: Self-developed [memorytest](https://github.com/oneclickvirt/memorytest) supporting sysbench, dd, winsat, mbw, stream
|
||||
- Disk test: Self-developed [disktest](https://github.com/oneclickvirt/disktest) supporting dd, fio, winsat
|
||||
- Streaming platform unlock tests concurrent query: Self-developed to [UnlockTests](https://github.com/oneclickvirt/UnlockTests), logic modified from [RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck) and others
|
||||
- IP quality/security information concurrent query: Self-developed, binary files compiled in [securityCheck](https://github.com/oneclickvirt/securityCheck)
|
||||
- Email port test: Self-developed [portchecker](https://github.com/oneclickvirt/portchecker)
|
||||
- Three-network return path test: Modified from [zhanghanyun/backtrace](https://github.com/zhanghanyun/backtrace) to [oneclickvirt/backtrace](https://github.com/oneclickvirt/backtrace)
|
||||
- Three-network route test: Modified from [NTrace-core](https://github.com/nxtrace/NTrace-core) to [nt3](https://github.com/oneclickvirt/nt3)
|
||||
- Speed test: Based on data from [speedtest.net](https://github.com/spiritLHLS/speedtest.net-CN-ID) and [speedtest.cn](https://github.com/spiritLHLS/speedtest.cn-CN-ID), developed to [oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest)
|
||||
- Three-network Ping test: Modified from [ecsspeed](https://github.com/spiritLHLS/ecsspeed) to [pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
- Support root or admin environment testing, support non-root or non-admin environment testing, support offline environment for testing, **not yet** support no DNS online environment for testing
|
||||
|
||||
**For first-time users of this project, it is recommended to check the instructions: [Jump to](https://github.com/oneclickvirt/ecs/blob/master/README_NEW_USER.md)**
|
||||
|
||||
---
|
||||
|
||||
## **Instructions for Use**
|
||||
|
||||
### **Linux/FreeBSD/MacOS**
|
||||
|
||||
#### **One-click command**
|
||||
|
||||
**One-Click Command** will **Not install Dependencies** by Default, **Not update Package Manager** by Default, **Non-Interactive Mode** by Default.
|
||||
|
||||
- **International users without acceleration:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs -l=en
|
||||
```
|
||||
|
||||
- **International/domestic users with CDN acceleration:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs -l=en
|
||||
```
|
||||
|
||||
- **Domestic users with CNB acceleration:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && export CN=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs -l=en
|
||||
```
|
||||
|
||||
- **Short Link:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://bash.spiritlhl.net/goecs -o goecs.sh && chmod +x goecs.sh && bash goecs.sh install && goecs -l=en
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://ba.sh/JrVa -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs -l=en
|
||||
```
|
||||
|
||||
**For more accurate testing, please follow the detailed instructions below to install and add non-essential dependencies**
|
||||
|
||||
#### **Detailed instructions**
|
||||
|
||||
The following commands control whether dependencies are installed, whether the package manager is updated, and whether interactive or non-interactive mode is used.
|
||||
|
||||
<details>
|
||||
<summary>Expand to view detailed instructions</summary>
|
||||
|
||||
1. **Download the script**
|
||||
|
||||
**International users without acceleration:**
|
||||
|
||||
```bash
|
||||
curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
**International/domestic users with CDN acceleration:**
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
**Domestic users with CNB acceleration:**
|
||||
|
||||
```bash
|
||||
export CN=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
2. **Update package manager (optional) and install environment**
|
||||
|
||||
```bash
|
||||
./goecs.sh env
|
||||
```
|
||||
|
||||
**Non-interactive mode:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && ./goecs.sh env
|
||||
```
|
||||
|
||||
3. **Install `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh install
|
||||
```
|
||||
|
||||
4. **Upgrade `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh upgrade
|
||||
```
|
||||
|
||||
5. **Uninstall `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh uninstall
|
||||
|
||||
6. **help command**
|
||||
|
||||
```bash
|
||||
./goecs.sh -h
|
||||
```
|
||||
|
||||
7. **Invoke the menu**
|
||||
|
||||
```bash
|
||||
goecs -l=en
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
#### **Command parameterization**
|
||||
|
||||
<details>
|
||||
<summary>Expand to view parameter descriptions</summary>
|
||||
|
||||
```bash
|
||||
Usage: goecs [options]
|
||||
-backtrace
|
||||
Enable/Disable backtrace test (in 'en' language or on windows it always false) (default true)
|
||||
-basic
|
||||
Enable/Disable basic test (default true)
|
||||
-ut
|
||||
Enable/Disable unlock media test (default true)
|
||||
-cpu
|
||||
Enable/Disable CPU test (default true)
|
||||
-cpum string
|
||||
Set CPU test method (supported: sysbench, geekbench, winsat) (default "sysbench")
|
||||
-cpu-method string
|
||||
Set CPU test method (supported: sysbench, geekbench, winsat) (default "sysbench")
|
||||
-cput string
|
||||
Set CPU test thread mode (supported: single, multi) (default "multi")
|
||||
-cpu-thread string
|
||||
Set CPU test thread mode (supported: single, multi) (default "multi")
|
||||
-disk
|
||||
Enable/Disable disk test (default true)
|
||||
-diskm string
|
||||
Set disk test method (supported: fio, dd, winsat) (default "fio")
|
||||
-disk-method string
|
||||
Set disk test method (supported: fio, dd, winsat) (default "fio")
|
||||
-diskmc
|
||||
Enable/Disable multiple disk checks, e.g., -diskmc=false
|
||||
-diskp string
|
||||
Set disk test path, e.g., -diskp /root
|
||||
-email
|
||||
Enable/Disable email port test (default true)
|
||||
-h Show help information
|
||||
-help
|
||||
Show help information
|
||||
-l string
|
||||
Set language (supported: en, zh) (default "zh")
|
||||
-lang string
|
||||
Set language (supported: en, zh) (default "zh")
|
||||
-log
|
||||
Enable/Disable logging in the current path
|
||||
-memory
|
||||
Enable/Disable memory test (default true)
|
||||
-memorym string
|
||||
Set memory test method (supported: stream, sysbench, dd, winsat, auto) (default "stream")
|
||||
-memory-method string
|
||||
Set memory test method (supported: stream, sysbench, dd, winsat, auto) (default "stream")
|
||||
-menu
|
||||
Enable/Disable menu mode, disable example: -menu=false (default true)
|
||||
-nt3
|
||||
Enable/Disable NT3 test (in 'en' language or on windows it always false) (default true)
|
||||
-nt3loc string
|
||||
Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all) (default "GZ")
|
||||
-nt3-location string
|
||||
Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all) (default "GZ")
|
||||
-nt3t string
|
||||
Set NT3 test type (supported: both, ipv4, ipv6) (default "ipv4")
|
||||
-nt3-type string
|
||||
Set NT3 test type (supported: both, ipv4, ipv6) (default "ipv4")
|
||||
-ping
|
||||
Enable/Disable ping test
|
||||
-security
|
||||
Enable/Disable security test (default true)
|
||||
-speed
|
||||
Enable/Disable speed test (default true)
|
||||
-spnum int
|
||||
Set the number of servers per operator for speed test (default 2)
|
||||
-tgdc
|
||||
Enable/Disable Telegram DC test
|
||||
-upload
|
||||
Enable/Disable upload the result (default true)
|
||||
-v Display version information
|
||||
-version
|
||||
Display version information
|
||||
-web
|
||||
Enable/Disable popular websites test
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### **Windows**
|
||||
|
||||
1. Download the compressed file with the .exe file: [Releases](https://github.com/oneclickvirt/ecs/releases)
|
||||
2. After unzipping, right-click and run as administrator.
|
||||
|
||||
PS: If it's a VM environment, it's OK not to run it in administrator mode, because VMs have no native testing tools and will automatically enable alternative methods for testing.
|
||||
PPS: Please refrain from downloading executable files labelled with a GUI for the time being, as they have not been fully adapted. The compressed packages for the CI version are unaffected.
|
||||
|
||||
---
|
||||
|
||||
### **Docker**
|
||||
|
||||
<details>
|
||||
<summary>Expand to view how to use it</summary>
|
||||
|
||||
International image: https://hub.docker.com/r/spiritlhl/goecs
|
||||
|
||||
Please ensure Docker is installed on your machine before executing the following commands
|
||||
|
||||
Privileged mode + host network
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host spiritlhl/goecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
Unprivileged mode + non-host network
|
||||
|
||||
```shell
|
||||
docker run --rm spiritlhl/goecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
Using Docker to execute tests will result in some hardware testing bias and virtualization architecture detection failure. Direct testing is recommended over Docker testing.
|
||||
|
||||
Mirror image: https://cnb.cool/oneclickvirt/ecs/-/packages/docker/ecs
|
||||
|
||||
Please ensure Docker is installed on your machine before executing the following commands
|
||||
|
||||
Privileged mode + host network
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host docker.cnb.cool/oneclickvirt/ecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
Unprivileged mode + non-host network
|
||||
|
||||
```shell
|
||||
docker run --rm docker.cnb.cool/oneclickvirt/ecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### Compiling from source code
|
||||
|
||||
<details>
|
||||
<summary>Expand to view compilation instructions</summary>
|
||||
|
||||
1. Clone the public branch of the repository (without private dependencies)
|
||||
```bash
|
||||
git clone -b public https://github.com/oneclickvirt/ecs.git
|
||||
cd ecs
|
||||
```
|
||||
|
||||
2. Install Go environment (skip if already installed)
|
||||
|
||||
Select go 1.25.4 version to install
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/spiritLHLS/one-click-installation-script/main/install_scripts/go.sh -o go.sh && chmod +x go.sh && bash go.sh
|
||||
```
|
||||
|
||||
3. Compile
|
||||
```bash
|
||||
go build -o goecs
|
||||
```
|
||||
|
||||
4. Run test
|
||||
```bash
|
||||
./goecs -menu=false -l=en
|
||||
```
|
||||
|
||||
Supported compilation parameters:
|
||||
- GOOS: supports linux, windows, darwin, freebsd, openbsd
|
||||
- GOARCH: supports amd64, arm, arm64, 386, mips, mipsle, s390x, riscv64
|
||||
|
||||
Cross-platform compilation examples:
|
||||
```bash
|
||||
# Compile Windows version
|
||||
GOOS=windows GOARCH=amd64 go build -o goecs.exe
|
||||
# Compile MacOS version
|
||||
GOOS=darwin GOARCH=amd64 go build -o goecs_darwin
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## QA
|
||||
|
||||
#### Q: Why is sysbench used by default instead of geekbench?
|
||||
|
||||
#### A: Comparing the characteristics of both:
|
||||
|
||||
| Comparison | sysbench | geekbench |
|
||||
|------------|----------|-----------|
|
||||
| Application scope | Lightweight, runs on almost any server | Heavyweight, won't run on small machines |
|
||||
| Test requirements | No network needed, no special hardware requirements | Requires internet, IPv4 environment, minimum 1GB memory |
|
||||
| Open source status | Based on LUA, open source, can compile for various architectures | Official binaries are closed source, cannot compile your own version |
|
||||
| Test stability | Core test components unchanged for 10+ years | Each major version updates test items, making scores hard to compare between versions (each version benchmarks against current best CPUs) |
|
||||
| Test content | Only tests computing performance | Covers multiple performance aspects with weighted scores, though some tests aren't commonly used |
|
||||
| Suitable scenarios | Good for quick tests, focuses on computing performance | Good for comprehensive testing |
|
||||
| Ranking | [sysbench.spiritlhl.net](https://sysbench.spiritlhl.net/) | [browser.geekbench.com](https://browser.geekbench.com/) |
|
||||
|
||||
Note that `goecs` allows you to specify CPU test method via parameters. The default is chosen for faster testing across more systems.
|
||||
|
||||
#### Q: Why use Golang instead of Rust for refactoring?
|
||||
|
||||
#### A: Because network-related projects currently trend toward Golang, with many components maintained by open source communities. Many Rust components would require building from scratch, ~~I'm too lazy~~ I don't have that technical capability.
|
||||
|
||||
#### Q: Why not continue developing the Shell version instead of refactoring?
|
||||
|
||||
#### A: Because there were too many varied environment issues. Pre-compiled binary files are easier for solving environment problems (better generalization).
|
||||
|
||||
#### Q: Are there explanations for each test item?
|
||||
|
||||
#### A: Each test project has its own maintenance repository. Click through to view the repository description.
|
||||
|
||||
#### Q: How do I manually terminate a test halfway through?
|
||||
|
||||
#### A: Press Ctrl+C to terminate the program. After termination, a goecs.txt file and share link will still be generated in the current directory containing information tested so far.
|
||||
|
||||
#### Q: How do I test in a non-Root environment?
|
||||
|
||||
#### A: Execute the installation command manually. If you can't install it, simply download the appropriate architecture package from releases, extract it, and run the file if you have execution permissions. Alternatively, use Docker if you can.
|
||||
|
||||
## Thanks
|
||||
|
||||
Thank [he.net](https://he.net) [bgp.tools](https://bgp.tools) [ipinfo.io](https://ipinfo.io) [maxmind.com](https://www.maxmind.com/en/home) [cloudflare.com](https://www.cloudflare.com/) [ip.sb](https://ip.sb) [scamalytics.com](https://scamalytics.com) [abuseipdb.com](https://www.abuseipdb.com/) [ip2location.com](https://ip2location.com/) [ip-api.com](https://ip-api.com) [ipregistry.co](https://ipregistry.co/) [ipdata.co](https://ipdata.co/) [ipgeolocation.io](https://ipgeolocation.io) [ipwhois.io](https://ipwhois.io) [ipapi.com](https://ipapi.com/) [ipapi.is](https://ipapi.is/) [ipqualityscore.com](https://www.ipqualityscore.com/) [bigdatacloud.com](https://www.bigdatacloud.com/) [dkly.net](https://data.dkly.net) [virustotal.com](https://www.virustotal.com/) [ipfighter.com](https://ipfighter.com/) [getipintel.net](http://check.getipintel.net/) [fraudlogix.com](https://fraudlogix.com) and others for providing APIs for testing, and thanks to various websites on the Internet for providing query resources.
|
||||
|
||||
Thank
|
||||
|
||||
<a href="https://h501.io/?from=69" target="_blank">
|
||||
<img src="https://github.com/spiritLHLS/ecs/assets/103393591/dfd47230-2747-4112-be69-b5636b34f07f" alt="h501" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
provided free hosting support for this open source project's shared test results storage
|
||||
|
||||
Thanks also to the following platforms for editorial and testing support
|
||||
|
||||
<a href="https://www.jetbrains.com/go/" target="_blank">
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand.png" alt="goland" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
<a href="https://community.ibm.com/zsystems/form/l1cc-oss-vm-request/" target="_blank">
|
||||
<img src="https://linuxone.cloud.marist.edu/oss/resources/images/linuxonelogo03.png" alt="ibm" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
<a href="https://console.zmto.com/?affid=1524" target="_blank">
|
||||
<img src="https://console.zmto.com/templates/2019/dist/images/logo_dark.svg" alt="zmto" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
## History Usage
|
||||
|
||||

|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://www.spiritlhl.net)
|
||||
|
||||
## License
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Foneclickvirt%2Fecs?ref=badge_large)
|
||||
|
||||
1173
README_NEW_USER.md
Normal file
1173
README_NEW_USER.md
Normal file
File diff suppressed because it is too large
Load Diff
442
README_ZH.md
Normal file
442
README_ZH.md
Normal file
@@ -0,0 +1,442 @@
|
||||
# ECS
|
||||
|
||||
[](https://github.com/oneclickvirt/ecs/actions/workflows/build_binary.yaml)
|
||||
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Foneclickvirt%2Fecs?ref=badge_shield)
|
||||
|
||||
[](https://hits.spiritlhl.net) [](https://github.com/oneclickvirt/ecs/releases)
|
||||
|
||||
融合怪测评项目 - GO版本
|
||||
|
||||
(仅环境安装[非必须]使用shell外无额外shell文件依赖,环境安装只是为了测的更准,极端情况下无环境依赖安装也可全测项目)
|
||||
|
||||
如有问题请 [issues](https://github.com/oneclickvirt/ecs/issues) 反馈。
|
||||
|
||||
Go 版本:[https://github.com/oneclickvirt/ecs](https://github.com/oneclickvirt/ecs)
|
||||
|
||||
Shell 版本:[https://github.com/spiritLHLS/ecs](https://github.com/spiritLHLS/ecs)
|
||||
|
||||
---
|
||||
|
||||
## **语言**
|
||||
|
||||
[English Docs](README.md) | [中文文档](README_ZH.md)
|
||||
|
||||
---
|
||||
|
||||
## **适配系统和架构**
|
||||
|
||||
### **编译与测试支持情况**
|
||||
| 编译支持的架构 | 测试支持的架构 | 编译支持的系统 | 测试支持的系统 |
|
||||
|---------------------------|--------------|---------------------------|---------------|
|
||||
| amd64 | amd64 | Linux | Linux |
|
||||
| arm64 | arm64 | Windows | Windows |
|
||||
| arm | | MacOS(Darwin) | MacOS |
|
||||
| 386 | | FreeBSD | |
|
||||
| mips,mipsle | | Android | |
|
||||
| mips64,mips64le | | | |
|
||||
| ppc64,ppc64le | | | |
|
||||
| s390x | s390x | | |
|
||||
| riscv64 | | | |
|
||||
|
||||
> 更多架构与系统请自行测试或编译,如有问题请开 issues。
|
||||
|
||||
### **待支持的系统**
|
||||
|
||||
| 系统 | 说明 |
|
||||
|----------------|---------------------------|
|
||||
| OpenBSD/NetBSD | 部分Golang的官方库未支持本系统(尤其是net相关项目) |
|
||||
|
||||
---
|
||||
|
||||
## **功能**
|
||||
|
||||
- 系统基础信息查询,IP基础信息并发查询:[basics](https://github.com/oneclickvirt/basics)、[gostun](https://github.com/oneclickvirt/gostun)
|
||||
- CPU 测试:[cputest](https://github.com/oneclickvirt/cputest),支持 sysbench(lua/golang版本)、geekbench、winsat
|
||||
- 内存测试:[memorytest](https://github.com/oneclickvirt/memorytest),支持 sysbench、dd、winsat、mbw、stream
|
||||
- 硬盘测试:[disktest](https://github.com/oneclickvirt/disktest),支持 dd、fio、winsat
|
||||
- 流媒体平台解锁测试并发查询:[UnlockTests](https://github.com/oneclickvirt/UnlockTests),逻辑借鉴 [RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck) 等
|
||||
- IP 质量/安全信息并发查询:二进制文件编译至 [securityCheck](https://github.com/oneclickvirt/securityCheck)
|
||||
- 邮件端口测试:[portchecker](https://github.com/oneclickvirt/portchecker)
|
||||
- 上游及回程路由线路检测:借鉴 [zhanghanyun/backtrace](https://github.com/zhanghanyun/backtrace),二次开发至 [oneclickvirt/backtrace](https://github.com/oneclickvirt/backtrace)
|
||||
- 三网路由测试:基于 [NTrace-core](https://github.com/nxtrace/NTrace-core),二次开发至 [nt3](https://github.com/oneclickvirt/nt3)
|
||||
- 网速测试:基于 [speedtest.net](https://github.com/spiritLHLS/speedtest.net-CN-ID) 和 [speedtest.cn](https://github.com/spiritLHLS/speedtest.cn-CN-ID) 数据,开发 [oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest),同时融合私有国内测速节点
|
||||
- 三网 Ping 值测试:借鉴 [ecsspeed](https://github.com/spiritLHLS/ecsspeed),二次开发至 [pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
- 支持root或admin环境下测试,支持非root或非admin环境下测试,支持离线环境下进行测试,**暂未**支持无DNS的在线环境下进行测试
|
||||
|
||||
**本项目初次使用建议查看说明:[跳转](https://github.com/oneclickvirt/ecs/blob/master/README_NEW_USER.md)**
|
||||
|
||||
---
|
||||
|
||||
## **使用说明**
|
||||
|
||||
### **Linux/FreeBSD/MacOS**
|
||||
|
||||
#### **一键命令**
|
||||
|
||||
**一键命令**将默认**不安装依赖**,默认**不更新包管理器**,默认**非互动模式**
|
||||
|
||||
- **国际用户无加速:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **国际/国内使用 CDN 加速:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **国内用户使用 CNB 加速:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && export CN=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **短链接:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://bash.spiritlhl.net/goecs -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://ba.sh/JrVa -o goecs.sh && chmod +x goecs.sh && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
**如果需要测试更准确,请按照下面的详细说明进行安装,添加非必需的依赖**
|
||||
|
||||
#### **详细说明**
|
||||
|
||||
以下命令可控制**是否安装依赖**,**是否更新包管理器**,**互动模式和非交互模式**
|
||||
|
||||
<details>
|
||||
<summary>展开查看详细说明</summary>
|
||||
|
||||
1. **下载脚本**
|
||||
|
||||
**国际用户无加速:**
|
||||
|
||||
```bash
|
||||
curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
**国际/国内使用 CDN 加速:**
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
**国内用户使用 CNB 加速:**
|
||||
|
||||
```bash
|
||||
export CN=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
2. **更新包管理器(可选择)并安装环境**
|
||||
|
||||
```bash
|
||||
./goecs.sh env
|
||||
```
|
||||
|
||||
**非互动模式:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && ./goecs.sh env
|
||||
```
|
||||
|
||||
3. **安装 `goecs` 本体(仅下载二进制文件无依赖安装)**
|
||||
|
||||
```bash
|
||||
./goecs.sh install
|
||||
```
|
||||
|
||||
4. **升级 `goecs` 本体**
|
||||
|
||||
```bash
|
||||
./goecs.sh upgrade
|
||||
```
|
||||
|
||||
5. **卸载 `goecs` 本体**
|
||||
|
||||
```bash
|
||||
./goecs.sh uninstall
|
||||
```
|
||||
|
||||
6. **帮助命令**
|
||||
|
||||
```bash
|
||||
./goecs.sh -h
|
||||
```
|
||||
|
||||
7. **唤起菜单**
|
||||
|
||||
```bash
|
||||
goecs
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
#### **命令参数化**
|
||||
|
||||
<details>
|
||||
<summary>展开查看各参数说明</summary>
|
||||
|
||||
```bash
|
||||
Usage: goecs [options]
|
||||
-backtrace
|
||||
Enable/Disable backtrace test (in 'en' language or on windows it always false) (default true)
|
||||
-basic
|
||||
Enable/Disable basic test (default true)
|
||||
-ut
|
||||
Enable/Disable unlock media test (default true)
|
||||
-cpu
|
||||
Enable/Disable CPU test (default true)
|
||||
-cpum string
|
||||
Set CPU test method (supported: sysbench, geekbench, winsat) (default "sysbench")
|
||||
-cpu-method string
|
||||
Set CPU test method (supported: sysbench, geekbench, winsat) (default "sysbench")
|
||||
-cput string
|
||||
Set CPU test thread mode (supported: single, multi) (default "multi")
|
||||
-cpu-thread string
|
||||
Set CPU test thread mode (supported: single, multi) (default "multi")
|
||||
-disk
|
||||
Enable/Disable disk test (default true)
|
||||
-diskm string
|
||||
Set disk test method (supported: fio, dd, winsat) (default "fio")
|
||||
-disk-method string
|
||||
Set disk test method (supported: fio, dd, winsat) (default "fio")
|
||||
-diskmc
|
||||
Enable/Disable multiple disk checks, e.g., -diskmc=false
|
||||
-diskp string
|
||||
Set disk test path, e.g., -diskp /root
|
||||
-email
|
||||
Enable/Disable email port test (default true)
|
||||
-h Show help information
|
||||
-help
|
||||
Show help information
|
||||
-l string
|
||||
Set language (supported: en, zh) (default "zh")
|
||||
-lang string
|
||||
Set language (supported: en, zh) (default "zh")
|
||||
-log
|
||||
Enable/Disable logging in the current path
|
||||
-memory
|
||||
Enable/Disable memory test (default true)
|
||||
-memorym string
|
||||
Set memory test method (supported: stream, sysbench, dd, winsat, auto) (default "stream")
|
||||
-memory-method string
|
||||
Set memory test method (supported: stream, sysbench, dd, winsat, auto) (default "stream")
|
||||
-menu
|
||||
Enable/Disable menu mode, disable example: -menu=false (default true)
|
||||
-nt3
|
||||
Enable/Disable NT3 test (in 'en' language or on windows it always false) (default true)
|
||||
-nt3loc string
|
||||
Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all) (default "GZ")
|
||||
-nt3-location string
|
||||
Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all) (default "GZ")
|
||||
-nt3t string
|
||||
Set NT3 test type (supported: both, ipv4, ipv6) (default "ipv4")
|
||||
-nt3-type string
|
||||
Set NT3 test type (supported: both, ipv4, ipv6) (default "ipv4")
|
||||
-ping
|
||||
Enable/Disable ping test
|
||||
-security
|
||||
Enable/Disable security test (default true)
|
||||
-speed
|
||||
Enable/Disable speed test (default true)
|
||||
-spnum int
|
||||
Set the number of servers per operator for speed test (default 2)
|
||||
-tgdc
|
||||
Enable/Disable Telegram DC test
|
||||
-upload
|
||||
Enable/Disable upload the result (default true)
|
||||
-v Display version information
|
||||
-version
|
||||
Display version information
|
||||
-web
|
||||
Enable/Disable popular websites test
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### **Windows**
|
||||
|
||||
1. 下载带 exe 文件的压缩包:[Releases](https://github.com/oneclickvirt/ecs/releases)
|
||||
2. 解压后,右键以管理员模式运行。
|
||||
|
||||
PS:如果是虚拟机环境,不以管理员模式运行也行,因为虚拟机无原生的测试工具,将自动启用替代方法测试。
|
||||
PPS: 暂时不要下载带GUI标签的exe文件,未完整适配,CI版本的压缩包是没问题的。
|
||||
|
||||
---
|
||||
|
||||
### **Docker**
|
||||
|
||||
<details>
|
||||
<summary>展开查看使用说明</summary>
|
||||
|
||||
国际镜像地址:https://hub.docker.com/r/spiritlhl/goecs
|
||||
|
||||
请确保执行下述命令前本机已安装Docker
|
||||
|
||||
特权模式+host网络
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host spiritlhl/goecs:latest -menu=false -l zh
|
||||
```
|
||||
|
||||
非特权模式+非host网络
|
||||
|
||||
```shell
|
||||
docker run --rm spiritlhl/goecs:latest -menu=false -l zh
|
||||
```
|
||||
|
||||
使用Docker执行测试,硬件测试会有一些偏差和虚拟化架构判断失效,还是推荐直接测试而不使用Docker测试。
|
||||
|
||||
国内阿里云镜像加速
|
||||
|
||||
请确保执行下述命令前本机已安装Docker
|
||||
|
||||
特权模式+host网络
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com/oneclickvirt/ecs:latest -menu=false -l zh
|
||||
```
|
||||
|
||||
非特权模式+非host网络
|
||||
|
||||
```shell
|
||||
docker run --rm crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com/oneclickvirt/ecs:latest -menu=false -l zh
|
||||
```
|
||||
|
||||
实际上还有CNB镜像地址 https://cnb.cool/oneclickvirt/ecs/-/packages/docker/ecs 但很可惜组织空间不足无法推送了,更推荐使用阿里云镜像加速
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### 从源码进行编译
|
||||
|
||||
<details>
|
||||
<summary>展开查看编译说明</summary>
|
||||
|
||||
1. 克隆仓库的 public 分支(不含私有依赖)
|
||||
```bash
|
||||
git clone -b public https://github.com/oneclickvirt/ecs.git
|
||||
cd ecs
|
||||
```
|
||||
|
||||
2. 安装 Go 环境(如已安装可跳过)
|
||||
|
||||
选择 go 1.25.4 的版本进行安装
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/spiritLHLS/one-click-installation-script/main/install_scripts/go.sh -o go.sh && chmod +x go.sh && bash go.sh
|
||||
```
|
||||
|
||||
3. 编译
|
||||
```bash
|
||||
go build -o goecs
|
||||
```
|
||||
|
||||
4. 运行测试
|
||||
```bash
|
||||
./goecs -menu=false -l zh
|
||||
```
|
||||
|
||||
支持的编译参数:
|
||||
- GOOS:支持 linux、windows、darwin、freebsd、openbsd
|
||||
- GOARCH:支持 amd64、arm、arm64、386、mips、mipsle、s390x、riscv64
|
||||
|
||||
跨平台编译示例:
|
||||
```bash
|
||||
# 编译 Windows 版本
|
||||
GOOS=windows GOARCH=amd64 go build -o goecs.exe
|
||||
# 编译 MacOS 版本
|
||||
GOOS=darwin GOARCH=amd64 go build -o goecs_darwin
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## QA
|
||||
|
||||
#### Q: 为什么默认使用sysbench而不是geekbench
|
||||
|
||||
#### A: 比较二者特点
|
||||
|
||||
| 比较项 | sysbench | geekbench |
|
||||
|------------------|----------|-----------|
|
||||
| 适用范围 | 轻量级,几乎可在任何服务器上运行 | 重量级,小型机器无法运行 |
|
||||
| 测试要求 | 无需网络,无特殊硬件需求 | 需联网,IPV4环境,至少1G内存 |
|
||||
| 开源情况 | 基于LUA,开源,可自行编译各架构版本 | 官方二进制闭源代码,不支持自行编译 |
|
||||
| 测试稳定性 | 核心测试组件10年以上未变 | 每个大版本更新测试项,分数不同版本间难以对比(每个版本对标当前最好的CPU) |
|
||||
| 测试内容 | 仅测试计算性能 | 覆盖多种性能测试,分数加权计算,但部分测试实际不常用 |
|
||||
| 适用场景 | 适合快速测试,仅测试计算性能 | 适合综合全面的测试 |
|
||||
| 排行榜 | [sysbench.spiritlhl.net](https://sysbench.spiritlhl.net/) | [browser.geekbench.com](https://browser.geekbench.com/) |
|
||||
|
||||
且```goecs```测试使用何种CPU测试方式可使用参数指定,默认只是为了更多用户快速测试的需求
|
||||
|
||||
#### Q: 为什么使用Golang而不是Rust重构
|
||||
|
||||
#### A: 因为网络相关的项目目前以Golang语言为趋势,大多组件有开源生态维护,Rust很多得自己手搓,~~我懒得搞~~我没那个技术力
|
||||
|
||||
#### Q: 为什么不继续开发Shell版本而是选择重构
|
||||
|
||||
#### A: 因为太多千奇百怪的环境问题了,还是提前编译好测试的二进制文件比较容易解决环境问题(泛化性更好)
|
||||
|
||||
#### Q: 每个测试项目的说明有吗?
|
||||
|
||||
#### A: 每个测试项目有对应的维护仓库,自行点击查看仓库说明
|
||||
|
||||
#### Q: 测试进行到一半如何手动终止?
|
||||
|
||||
#### A: 按ctrl键和c键终止程序,终止后依然会在当前目录下生成goecs.txt文件和分享链接,里面是已经测试到的信息。
|
||||
|
||||
#### Q: 非Root环境如何进行测试?
|
||||
|
||||
#### A: 手动执行安装命令,实在装不上也没问题,直接在release中下载对应架构的压缩包解压后执行即可,只要你能执行的了文件。或者你能使用docker的话用docker执行。
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢
|
||||
[DKLYDataHub - IP Geolocation Data](https://data.dkly.net)
|
||||
[he.net](https://he.net) [bgp.tools](https://bgp.tools) [ipinfo.io](https://ipinfo.io) [maxmind.com](https://www.maxmind.com/en/home) [cloudflare.com](https://www.cloudflare.com/) [ip.sb](https://ip.sb) [scamalytics.com](https://scamalytics.com) [abuseipdb.com](https://www.abuseipdb.com/) [ip2location.com](https://ip2location.com/) [ip-api.com](https://ip-api.com) [ipregistry.co](https://ipregistry.co/) [ipdata.co](https://ipdata.co/) [ipgeolocation.io](https://ipgeolocation.io) [ipwhois.io](https://ipwhois.io) [ipapi.com](https://ipapi.com/) [ipapi.is](https://ipapi.is/) [ipqualityscore.com](https://www.ipqualityscore.com/) [bigdatacloud.com](https://www.bigdatacloud.com/) [virustotal.com](https://www.virustotal.com/) [ipfighter.com](https://ipfighter.com/) [getipintel.net](http://check.getipintel.net/) [fraudlogix.com](https://fraudlogix.com) 等网站提供的API进行检测,感谢互联网各网站提供的查询资源
|
||||
|
||||
感谢
|
||||
|
||||
<a href="https://h501.io/?from=69" target="_blank">
|
||||
<img src="https://github.com/spiritLHLS/ecs/assets/103393591/dfd47230-2747-4112-be69-b5636b34f07f" alt="h501" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
提供的免费托管支持本开源项目的共享测试结果存储
|
||||
|
||||
同时感谢以下平台提供编辑和测试支持
|
||||
|
||||
<a href="https://www.jetbrains.com/go/" target="_blank">
|
||||
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand.png" alt="goland" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
<a href="https://community.ibm.com/zsystems/form/l1cc-oss-vm-request/" target="_blank">
|
||||
<img src="https://linuxone.cloud.marist.edu/oss/resources/images/linuxonelogo03.png" alt="ibm" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
<a href="https://console.zmto.com/?affid=1524" target="_blank">
|
||||
<img src="https://console.zmto.com/templates/2019/dist/images/logo_dark.svg" alt="zmto" style="height: 50px;">
|
||||
</a>
|
||||
|
||||
## History Usage
|
||||
|
||||

|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://www.spiritlhl.net)
|
||||
|
||||
## License
|
||||
[](https://app.fossa.com/projects/git%2Bgithub.com%2Foneclickvirt%2Fecs?ref=badge_large)
|
||||
56
api/api.go
Normal file
56
api/api.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package api
|
||||
|
||||
const (
|
||||
// Version API版本号
|
||||
Version = "v1.0.0"
|
||||
|
||||
// DefaultVersion 默认的ECS版本号
|
||||
DefaultVersion = "v0.1.114"
|
||||
)
|
||||
|
||||
// 测试方法常量
|
||||
const (
|
||||
// CPU测试方法
|
||||
CpuMethodSysbench = "sysbench"
|
||||
CpuMethodGeekbench = "geekbench"
|
||||
CpuMethodWinsat = "winsat"
|
||||
|
||||
// 内存测试方法
|
||||
MemoryMethodStream = "stream"
|
||||
MemoryMethodSysbench = "sysbench"
|
||||
MemoryMethodDD = "dd"
|
||||
MemoryMethodWinsat = "winsat"
|
||||
|
||||
// 硬盘测试方法
|
||||
DiskMethodFio = "fio"
|
||||
DiskMethodDD = "dd"
|
||||
DiskMethodWinsat = "winsat"
|
||||
|
||||
// 线程模式
|
||||
ThreadModeSingle = "single"
|
||||
ThreadModeMulti = "multi"
|
||||
|
||||
// 语言选项
|
||||
LanguageZH = "zh"
|
||||
LanguageEN = "en"
|
||||
|
||||
// IP检测类型
|
||||
CheckTypeIPv4 = "ipv4"
|
||||
CheckTypeIPv6 = "ipv6"
|
||||
CheckTypeAuto = "auto"
|
||||
|
||||
// 测速平台
|
||||
PlatformCN = "cn"
|
||||
PlatformNet = "net"
|
||||
|
||||
// 运营商类型
|
||||
OperatorCMCC = "cmcc" // 中国移动
|
||||
OperatorCU = "cu" // 中国联通
|
||||
OperatorCT = "ct" // 中国电信
|
||||
OperatorGlobal = "global" // 全球节点
|
||||
OperatorOther = "other" // 其他
|
||||
OperatorHK = "hk" // 香港
|
||||
OperatorTW = "tw" // 台湾
|
||||
OperatorJP = "jp" // 日本
|
||||
OperatorSG = "sg" // 新加坡
|
||||
)
|
||||
259
api/config.go
Normal file
259
api/config.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
)
|
||||
|
||||
// Config 配置接口,导出用于外部调用
|
||||
type Config = params.Config
|
||||
|
||||
// NewConfig 创建默认配置
|
||||
// version: 版本号字符串
|
||||
func NewConfig(version string) *Config {
|
||||
return params.NewConfig(version)
|
||||
}
|
||||
|
||||
// NewDefaultConfig 创建默认配置(使用默认版本号)
|
||||
func NewDefaultConfig() *Config {
|
||||
return params.NewConfig("v0.1.114")
|
||||
}
|
||||
|
||||
// ConfigOption 配置选项函数类型
|
||||
type ConfigOption func(*Config)
|
||||
|
||||
// WithLanguage 设置语言
|
||||
func WithLanguage(lang string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Language = lang
|
||||
}
|
||||
}
|
||||
|
||||
// WithCpuTestMethod 设置CPU测试方法
|
||||
// method: "sysbench" 或 "geekbench"
|
||||
func WithCpuTestMethod(method string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.CpuTestMethod = method
|
||||
}
|
||||
}
|
||||
|
||||
// WithCpuTestThreadMode 设置CPU测试线程模式
|
||||
// mode: "single" 或 "multi"
|
||||
func WithCpuTestThreadMode(mode string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.CpuTestThreadMode = mode
|
||||
}
|
||||
}
|
||||
|
||||
// WithMemoryTestMethod 设置内存测试方法
|
||||
// method: "stream", "sysbench", "dd"
|
||||
func WithMemoryTestMethod(method string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.MemoryTestMethod = method
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiskTestMethod 设置硬盘测试方法
|
||||
// method: "fio" 或 "dd"
|
||||
func WithDiskTestMethod(method string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.DiskTestMethod = method
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiskTestPath 设置硬盘测试路径
|
||||
func WithDiskTestPath(path string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.DiskTestPath = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiskMultiCheck 设置是否进行硬盘多路径检测
|
||||
func WithDiskMultiCheck(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.DiskMultiCheck = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpeedTestNum 设置测速节点数量
|
||||
func WithSpeedTestNum(num int) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.SpNum = num
|
||||
}
|
||||
}
|
||||
|
||||
// WithWidth 设置输出宽度
|
||||
func WithWidth(width int) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Width = width
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilePath 设置输出文件路径
|
||||
func WithFilePath(path string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.FilePath = path
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableUpload 设置是否启用上传
|
||||
func WithEnableUpload(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.EnableUpload = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnableLogger 设置是否启用日志
|
||||
func WithEnableLogger(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.EnableLogger = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithBasicTest 设置是否执行基础信息测试
|
||||
func WithBasicTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.BasicStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithCpuTest 设置是否执行CPU测试
|
||||
func WithCpuTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.CpuTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithMemoryTest 设置是否执行内存测试
|
||||
func WithMemoryTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.MemoryTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithDiskTest 设置是否执行硬盘测试
|
||||
func WithDiskTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.DiskTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnlockTest 设置是否执行流媒体解锁测试
|
||||
func WithUnlockTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.UtTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSecurityTest 设置是否执行IP质量测试
|
||||
func WithSecurityTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.SecurityTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithEmailTest 设置是否执行邮件端口测试
|
||||
func WithEmailTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.EmailTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithBacktraceTest 设置是否执行回程路由测试
|
||||
func WithBacktraceTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.BacktraceStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithNt3Test 设置是否执行三网路由测试
|
||||
func WithNt3Test(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Nt3Status = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpeedTest 设置是否执行测速测试
|
||||
func WithSpeedTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.SpeedTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithPingTest 设置是否执行PING测试
|
||||
func WithPingTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.PingTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithTgdcTest 设置是否执行Telegram DC测试
|
||||
func WithTgdcTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.TgdcTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithWebTest 设置是否执行网站测试
|
||||
func WithWebTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.WebTestStatus = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithNt3CheckType 设置三网路由检测类型
|
||||
// checkType: "ipv4", "ipv6" 或 "auto"
|
||||
func WithNt3CheckType(checkType string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Nt3CheckType = checkType
|
||||
}
|
||||
}
|
||||
|
||||
// WithNt3Location 设置三网路由检测位置
|
||||
func WithNt3Location(location string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Nt3Location = location
|
||||
}
|
||||
}
|
||||
|
||||
// WithAutoChangeDiskMethod 设置是否自动切换硬盘测试方法
|
||||
func WithAutoChangeDiskMethod(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.AutoChangeDiskMethod = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnlyChinaTest 设置是否只进行国内测试
|
||||
func WithOnlyChinaTest(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.OnlyChinaTest = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithMenuMode 设置是否启用菜单模式
|
||||
func WithMenuMode(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.MenuMode = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithOnlyIpInfoCheck 设置是否只进行IP信息检测
|
||||
func WithOnlyIpInfoCheck(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.OnlyIpInfoCheck = enable
|
||||
}
|
||||
}
|
||||
|
||||
// WithChoice 设置菜单选择
|
||||
func WithChoice(choice string) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.Choice = choice
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyOptions 应用配置选项
|
||||
func ApplyOptions(config *Config, options ...ConfigOption) *Config {
|
||||
for _, opt := range options {
|
||||
opt(config)
|
||||
}
|
||||
return config
|
||||
}
|
||||
27
api/menu.go
Normal file
27
api/menu.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/ecs/internal/menu"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
// GetMenuChoice 获取用户菜单选择
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// 返回: 用户选择的选项
|
||||
func GetMenuChoice(language string) string {
|
||||
return menu.GetMenuChoice(language)
|
||||
}
|
||||
|
||||
// PrintMenuOptions 打印菜单选项
|
||||
// preCheck: 网络检查结果
|
||||
// config: 配置对象
|
||||
func PrintMenuOptions(preCheck utils.NetCheckResult, config *Config) {
|
||||
menu.PrintMenuOptions(preCheck, config)
|
||||
}
|
||||
|
||||
// HandleMenuMode 处理菜单模式
|
||||
// preCheck: 网络检查结果
|
||||
// config: 配置对象
|
||||
func HandleMenuMode(preCheck utils.NetCheckResult, config *Config) {
|
||||
menu.HandleMenuMode(preCheck, config)
|
||||
}
|
||||
187
api/runner.go
Normal file
187
api/runner.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/ecs/internal/runner"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
// RunResult 运行结果
|
||||
type RunResult struct {
|
||||
Output string // 完整输出
|
||||
Duration time.Duration // 运行时长
|
||||
StartTime time.Time // 开始时间
|
||||
EndTime time.Time // 结束时间
|
||||
}
|
||||
|
||||
// RunAllTests 执行所有测试(高级接口)
|
||||
// preCheck: 网络检查结果
|
||||
// config: 配置对象
|
||||
// 返回: 运行结果
|
||||
func RunAllTests(preCheck utils.NetCheckResult, config *Config) *RunResult {
|
||||
var (
|
||||
wg1, wg2, wg3 sync.WaitGroup
|
||||
basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo string
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
infoMutex sync.Mutex
|
||||
)
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
switch config.Language {
|
||||
case "zh":
|
||||
runner.RunChineseTests(preCheck, config, &wg1, &wg2, &wg3,
|
||||
&basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo,
|
||||
&output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
case "en":
|
||||
runner.RunEnglishTests(preCheck, config, &wg1, &wg2, &wg3,
|
||||
&basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo,
|
||||
&output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
default:
|
||||
runner.RunChineseTests(preCheck, config, &wg1, &wg2, &wg3,
|
||||
&basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo,
|
||||
&output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
}
|
||||
|
||||
endTime := time.Now()
|
||||
|
||||
return &RunResult{
|
||||
Output: output,
|
||||
Duration: endTime.Sub(startTime),
|
||||
StartTime: startTime,
|
||||
EndTime: endTime,
|
||||
}
|
||||
}
|
||||
|
||||
// RunBasicTests 运行基础信息测试
|
||||
func RunBasicTests(preCheck utils.NetCheckResult, config *Config) string {
|
||||
var (
|
||||
basicInfo, securityInfo string
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunBasicTests(preCheck, config, &basicInfo, &securityInfo, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunCPUTest 运行CPU测试
|
||||
func RunCPUTest(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunCPUTest(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunMemoryTest 运行内存测试
|
||||
func RunMemoryTest(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunMemoryTest(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunDiskTest 运行硬盘测试
|
||||
func RunDiskTest(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunDiskTest(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunIpInfoCheck 执行IP信息检测
|
||||
func RunIpInfoCheck(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunIpInfoCheck(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunStreamingTests 运行流媒体测试
|
||||
func RunStreamingTests(config *Config, mediaInfo string) string {
|
||||
var (
|
||||
wg1 sync.WaitGroup
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
infoMutex sync.Mutex
|
||||
)
|
||||
return runner.RunStreamingTests(config, &wg1, &mediaInfo, output, tempOutput, &outputMutex, &infoMutex)
|
||||
}
|
||||
|
||||
// RunSecurityTests 运行安全测试
|
||||
func RunSecurityTests(config *Config, securityInfo string) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunSecurityTests(config, securityInfo, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunEmailTests 运行邮件端口测试
|
||||
func RunEmailTests(config *Config, emailInfo string) string {
|
||||
var (
|
||||
wg2 sync.WaitGroup
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
infoMutex sync.Mutex
|
||||
)
|
||||
return runner.RunEmailTests(config, &wg2, &emailInfo, output, tempOutput, &outputMutex, &infoMutex)
|
||||
}
|
||||
|
||||
// RunNetworkTests 运行网络测试(中文模式)
|
||||
func RunNetworkTests(config *Config, ptInfo string) string {
|
||||
var (
|
||||
wg3 sync.WaitGroup
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
infoMutex sync.Mutex
|
||||
)
|
||||
return runner.RunNetworkTests(config, &wg3, &ptInfo, output, tempOutput, &outputMutex, &infoMutex)
|
||||
}
|
||||
|
||||
// RunSpeedTests 运行测速测试(中文模式)
|
||||
func RunSpeedTests(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunSpeedTests(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunEnglishNetworkTests 运行网络测试(英文模式)
|
||||
func RunEnglishNetworkTests(config *Config, ptInfo string) string {
|
||||
var (
|
||||
wg3 sync.WaitGroup
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunEnglishNetworkTests(config, &wg3, &ptInfo, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// RunEnglishSpeedTests 运行测速测试(英文模式)
|
||||
func RunEnglishSpeedTests(config *Config) string {
|
||||
var (
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.RunEnglishSpeedTests(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
// AppendTimeInfo 添加时间信息
|
||||
func AppendTimeInfo(config *Config, output string, startTime time.Time) string {
|
||||
var (
|
||||
tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
)
|
||||
return runner.AppendTimeInfo(config, output, tempOutput, startTime, &outputMutex)
|
||||
}
|
||||
|
||||
// HandleUploadResults 处理上传结果
|
||||
func HandleUploadResults(config *Config, output string) {
|
||||
runner.HandleUploadResults(config, output)
|
||||
}
|
||||
101
api/tests.go
Normal file
101
api/tests.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/ecs/internal/tests"
|
||||
)
|
||||
|
||||
// TestResult 测试结果结构
|
||||
type TestResult struct {
|
||||
TestMethod string // 实际使用的测试方法
|
||||
Output string // 测试输出结果
|
||||
Success bool // 是否成功
|
||||
Error error // 错误信息
|
||||
}
|
||||
|
||||
// CpuTest CPU测试公共接口
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// testMethod: 测试方法 ("sysbench" 或 "geekbench")
|
||||
// testThread: 线程模式 ("single" 或 "multi")
|
||||
// 返回: (实际测试方法, 测试结果)
|
||||
func CpuTest(language, testMethod, testThread string) (string, string) {
|
||||
return tests.CpuTest(language, testMethod, testThread)
|
||||
}
|
||||
|
||||
// MemoryTest 内存测试公共接口
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// testMethod: 测试方法 ("stream", "sysbench", "dd")
|
||||
// 返回: (实际测试方法, 测试结果)
|
||||
func MemoryTest(language, testMethod string) (string, string) {
|
||||
return tests.MemoryTest(language, testMethod)
|
||||
}
|
||||
|
||||
// DiskTest 硬盘测试公共接口
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// testMethod: 测试方法 ("fio" 或 "dd")
|
||||
// testPath: 测试路径
|
||||
// isMultiCheck: 是否多路径检测
|
||||
// autoChange: 是否自动切换方法
|
||||
// 返回: (实际测试方法, 测试结果)
|
||||
func DiskTest(language, testMethod, testPath string, isMultiCheck, autoChange bool) (string, string) {
|
||||
return tests.DiskTest(language, testMethod, testPath, isMultiCheck, autoChange)
|
||||
}
|
||||
|
||||
// MediaTest 流媒体解锁测试公共接口
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// 返回: 测试结果
|
||||
func MediaTest(language string) string {
|
||||
return tests.MediaTest(language)
|
||||
}
|
||||
|
||||
// SpeedTestShowHead 显示测速表头
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
func SpeedTestShowHead(language string) {
|
||||
tests.ShowHead(language)
|
||||
}
|
||||
|
||||
// SpeedTestNearby 就近节点测速
|
||||
func SpeedTestNearby() {
|
||||
tests.NearbySP()
|
||||
}
|
||||
|
||||
// SpeedTestCustom 自定义测速
|
||||
// platform: 平台 ("cn" 或 "net")
|
||||
// operator: 运营商 ("cmcc", "cu", "ct", "global", "other" 等)
|
||||
// num: 测试节点数量
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
func SpeedTestCustom(platform, operator string, num int, language string) {
|
||||
tests.CustomSP(platform, operator, num, language)
|
||||
}
|
||||
|
||||
// NextTrace3Check 三网路由追踪测试
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// location: 位置
|
||||
// checkType: 检测类型 ("ipv4", "ipv6")
|
||||
func NextTrace3Check(language, location, checkType string) {
|
||||
tests.NextTrace3Check(language, location, checkType)
|
||||
}
|
||||
|
||||
// UpstreamsCheck 上游及回程线路检测
|
||||
func UpstreamsCheck(language string) {
|
||||
tests.UpstreamsCheck(language)
|
||||
}
|
||||
|
||||
// GetIPv4Address 获取当前IPv4地址
|
||||
func GetIPv4Address() string {
|
||||
return tests.IPV4
|
||||
}
|
||||
|
||||
// GetIPv6Address 获取当前IPv6地址
|
||||
func GetIPv6Address() string {
|
||||
return tests.IPV6
|
||||
}
|
||||
|
||||
// SetIPv4Address 设置IPv4地址(用于测试)
|
||||
func SetIPv4Address(ipv4 string) {
|
||||
tests.IPV4 = ipv4
|
||||
}
|
||||
|
||||
// SetIPv6Address 设置IPv6地址(用于测试)
|
||||
func SetIPv6Address(ipv6 string) {
|
||||
tests.IPV6 = ipv6
|
||||
}
|
||||
91
api/utils.go
Normal file
91
api/utils.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
// NetCheckResult 网络检查结果
|
||||
type NetCheckResult = utils.NetCheckResult
|
||||
|
||||
// StatsResponse 统计信息响应
|
||||
type StatsResponse = utils.StatsResponse
|
||||
|
||||
// GitHubRelease GitHub发布信息
|
||||
type GitHubRelease = utils.GitHubRelease
|
||||
|
||||
// CheckPublicAccess 检查公网访问能力
|
||||
// timeout: 超时时间
|
||||
// 返回: 网络检查结果
|
||||
func CheckPublicAccess(timeout time.Duration) NetCheckResult {
|
||||
return utils.CheckPublicAccess(timeout)
|
||||
}
|
||||
|
||||
// GetGoescStats 获取goecs统计信息
|
||||
// 返回: (统计响应, 错误)
|
||||
func GetGoescStats() (*StatsResponse, error) {
|
||||
return utils.GetGoescStats()
|
||||
}
|
||||
|
||||
// GetLatestEcsRelease 获取最新的ECS版本信息
|
||||
// 返回: (GitHub发布信息, 错误)
|
||||
func GetLatestEcsRelease() (*GitHubRelease, error) {
|
||||
return utils.GetLatestEcsRelease()
|
||||
}
|
||||
|
||||
// PrintHead 打印程序头部信息
|
||||
// language: 语言 ("zh" 或 "en")
|
||||
// width: 显示宽度
|
||||
// version: 版本号
|
||||
func PrintHead(language string, width int, version string) {
|
||||
utils.PrintHead(language, width, version)
|
||||
}
|
||||
|
||||
// PrintCenteredTitle 打印居中标题
|
||||
// title: 标题文本
|
||||
// width: 显示宽度
|
||||
func PrintCenteredTitle(title string, width int) {
|
||||
utils.PrintCenteredTitle(title, width)
|
||||
}
|
||||
|
||||
// ProcessAndUpload 处理并上传结果
|
||||
// output: 输出内容
|
||||
// filePath: 文件路径
|
||||
// enableUpload: 是否启用上传
|
||||
// 返回: (HTTP URL, HTTPS URL)
|
||||
func ProcessAndUpload(output, filePath string, enableUpload bool, language string) (string, string) {
|
||||
return utils.ProcessAndUpload(output, filePath, enableUpload, language)
|
||||
}
|
||||
|
||||
// BasicsAndSecurityCheck 基础信息和安全检查
|
||||
// language: 语言
|
||||
// checkType: 检查类型
|
||||
// securityTestStatus: 是否执行安全测试
|
||||
// 返回: (IPv4地址, IPv6地址, 基础信息, 安全信息, 检查类型)
|
||||
func BasicsAndSecurityCheck(language, checkType string, securityTestStatus bool) (string, string, string, string, string) {
|
||||
return utils.BasicsAndSecurityCheck(language, checkType, securityTestStatus)
|
||||
}
|
||||
|
||||
// OnlyBasicsIpInfo 仅获取基础IP信息
|
||||
// language: 语言
|
||||
// 返回: (IPv4地址, IPv6地址, IP信息)
|
||||
func OnlyBasicsIpInfo(language string) (string, string, string) {
|
||||
return utils.OnlyBasicsIpInfo(language)
|
||||
}
|
||||
|
||||
// FormatGoecsNumber 格式化数字显示
|
||||
// num: 数字
|
||||
// 返回: 格式化后的字符串
|
||||
func FormatGoecsNumber(num int) string {
|
||||
return utils.FormatGoecsNumber(num)
|
||||
}
|
||||
|
||||
// PrintAndCapture 打印并捕获输出
|
||||
// fn: 执行的函数
|
||||
// tempOutput: 临时输出
|
||||
// existingOutput: 现有输出
|
||||
// 返回: 捕获的输出
|
||||
func PrintAndCapture(fn func(), tempOutput, existingOutput string) string {
|
||||
return utils.PrintAndCapture(fn, tempOutput, existingOutput)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package backtrace
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/backtrace/bk"
|
||||
)
|
||||
|
||||
func BackTrace() {
|
||||
backtrace.BackTrace()
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package backtrace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
//func TestGeneratePrefixMap(t *testing.T) {
|
||||
// prefix := "223.119.8.0/21"
|
||||
// prefixList := GeneratePrefixList(prefix)
|
||||
// if prefixList != nil {
|
||||
// // 打印生成的IP地址前缀列表
|
||||
// for _, ip := range prefixList {
|
||||
// fmt.Println(ip)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
func TestBackTrace(t *testing.T) {
|
||||
BackTrace()
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package basic1
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_basic(t *testing.T) {
|
||||
Basic("zh")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package basic1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/basics/network"
|
||||
"github.com/oneclickvirt/basics/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 使用gopsutil查询可能会特别慢,执行命令查询反而更快
|
||||
// TODO
|
||||
// 迁移Shell的完整检测逻辑使用执行命令的方式查询,最后都失败才使用gopsutil查询
|
||||
// 本包不在main中使用
|
||||
func Basic(language string) {
|
||||
ipInfo, _, _ := network.NetworkCheck("both", false, language)
|
||||
systemInfo := system.CheckSystemInfo(language)
|
||||
basicInfo := strings.ReplaceAll(systemInfo+ipInfo, "\n\n", "\n")
|
||||
fmt.Printf(basicInfo)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package commediatest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/CommonMediaTests/commediatests"
|
||||
)
|
||||
|
||||
func ComMediaTest(language string) {
|
||||
res := commediatests.MediaTests(language)
|
||||
fmt.Printf(res)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package commediatest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMedia(t *testing.T) {
|
||||
ComMediaTest("zh")
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package cputest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/cputest/cpu"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CpuTest(language, testMethod, testThread string) {
|
||||
var res string
|
||||
if runtime.GOOS == "windows" {
|
||||
if testMethod != "winsat" && testMethod != "" {
|
||||
res = "Detected host is Windows, using Winsat for testing.\n"
|
||||
}
|
||||
res += cpu.WinsatTest(language, testThread)
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "sysbench":
|
||||
res = cpu.SysBenchTest(language, testThread)
|
||||
if res == "" {
|
||||
res = "Sysbench test failed, switching to Geekbench for testing.\n"
|
||||
res += cpu.GeekBenchTest(language, testThread)
|
||||
}
|
||||
case "geekbench":
|
||||
res = cpu.GeekBenchTest(language, testThread)
|
||||
if res == "" {
|
||||
res = "Geekbench test failed, switching to Sysbench for testing.\n"
|
||||
res += cpu.SysBenchTest(language, testThread)
|
||||
}
|
||||
default:
|
||||
res = "Invalid test method specified.\n"
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
fmt.Print(res)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package cputest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
CpuTest("zh", "sysbench", "1")
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package disktest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/disktest/disk"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DiskTest(language, testMethod, testPath string, isMultiCheck bool) {
|
||||
var res string
|
||||
if runtime.GOOS == "windows" {
|
||||
if testMethod != "winsat" && testMethod != "" {
|
||||
res = "Detected host is Windows, using Winsat for testing.\n"
|
||||
}
|
||||
res = disk.WinsatTest(language, isMultiCheck, testPath)
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "fio":
|
||||
res = disk.FioTest(language, isMultiCheck, testPath)
|
||||
if res == "" {
|
||||
res = "Fio test failed, switching to DD for testing.\n"
|
||||
res += disk.DDTest(language, isMultiCheck, testPath)
|
||||
}
|
||||
case "dd":
|
||||
res = disk.DDTest(language, isMultiCheck, testPath)
|
||||
if res == "" {
|
||||
res = "DD test failed, switching to Fio for testing.\n"
|
||||
res += disk.FioTest(language, isMultiCheck, testPath)
|
||||
}
|
||||
default:
|
||||
res = "Unsupported test method specified.\n"
|
||||
}
|
||||
}
|
||||
//fmt.Println("--------------------------------------------------")
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
fmt.Printf(res)
|
||||
//fmt.Println("--------------------------------------------------")
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package disktest
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDiskIoTest(t *testing.T) {
|
||||
DiskTest("zh", "sysbench", "", false)
|
||||
}
|
||||
149
go.mod
149
go.mod
@@ -1,96 +1,125 @@
|
||||
module github.com/oneclickvirt/ecs
|
||||
|
||||
go 1.22.4
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
github.com/imroc/req/v3 v3.43.7
|
||||
github.com/oneclickvirt/CommonMediaTests v0.0.2-20240630023003
|
||||
github.com/oneclickvirt/UnlockTests v0.0.10-20240630044930
|
||||
github.com/oneclickvirt/backtrace v0.0.4-20240624090335
|
||||
github.com/oneclickvirt/basics v0.0.3-20240625075226
|
||||
github.com/oneclickvirt/cputest v0.0.6-20240630144058
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
github.com/imroc/req/v3 v3.54.0
|
||||
github.com/oneclickvirt/UnlockTests v0.0.35-20260207053956
|
||||
github.com/oneclickvirt/backtrace v0.0.8-20251109090457
|
||||
github.com/oneclickvirt/basics v0.0.16-20251112033526
|
||||
github.com/oneclickvirt/cputest v0.0.12-20251111095842
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446
|
||||
github.com/oneclickvirt/disktest v0.0.3-20240629152513
|
||||
github.com/oneclickvirt/memorytest v0.0.1-20240624151629
|
||||
github.com/oneclickvirt/nt3 v0.0.2-20240630152642
|
||||
github.com/oneclickvirt/portchecker v0.0.1-20240624155429
|
||||
github.com/oneclickvirt/security v0.0.2-20240630142358
|
||||
github.com/oneclickvirt/speedtest v0.0.6-20240630114816
|
||||
github.com/oneclickvirt/disktest v0.0.10-20250924030424
|
||||
github.com/oneclickvirt/gostun v0.0.5-20250727155022
|
||||
github.com/oneclickvirt/memorytest v0.0.10-20251218032900
|
||||
github.com/oneclickvirt/nt3 v0.0.11-20260112140912
|
||||
github.com/oneclickvirt/pingtest v0.0.9-20251104112920
|
||||
github.com/oneclickvirt/portchecker v0.0.3-20250728015900
|
||||
github.com/oneclickvirt/privatespeedtest v0.0.1-20260112130218
|
||||
github.com/oneclickvirt/security v0.0.8-20260202071316
|
||||
github.com/oneclickvirt/speedtest v0.0.11-20251102151740
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.9.2 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/cloudflare/circl v1.3.9 // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.2.0 // indirect
|
||||
github.com/google/gopacket v1.1.19 // indirect
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 // indirect
|
||||
github.com/gorilla/websocket v1.5.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huin/goupnp v1.2.0 // indirect
|
||||
github.com/icholy/digest v1.1.0 // indirect
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/jaypipes/ghw v0.17.0 // indirect
|
||||
github.com/jaypipes/pcidb v1.0.1 // indirect
|
||||
github.com/jsdelivr/globalping-cli v1.5.1 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||
github.com/libp2p/go-nat v0.2.0 // indirect
|
||||
github.com/libp2p/go-netroute v0.2.1 // indirect
|
||||
github.com/lionsoul2014/ip2region v2.11.2+incompatible // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/miekg/dns v1.1.61 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/nxtrace/NTrace-core v1.3.1 // indirect
|
||||
github.com/oneclickvirt/gostun v0.0.2-20240625025941 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/nxtrace/NTrace-core v1.5.0 // indirect
|
||||
github.com/oneclickvirt/dd v0.0.2-20250808062818 // indirect
|
||||
github.com/oneclickvirt/fio v0.0.2-20250808045755 // indirect
|
||||
github.com/oneclickvirt/mbw v0.0.1-20250808061222 // indirect
|
||||
github.com/oneclickvirt/stream v0.0.2-20250924154001 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pion/dtls/v2 v2.2.7 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/stun/v2 v2.0.0 // indirect
|
||||
github.com/pion/transport/v2 v2.2.1 // indirect
|
||||
github.com/pion/transport/v3 v3.0.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/quic-go v0.45.1 // indirect
|
||||
github.com/refraction-networking/utls v1.6.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/prometheus-community/pro-bing v0.4.1 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
||||
github.com/refraction-networking/utls v1.7.3 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rodaine/table v1.2.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.14.4 // indirect
|
||||
github.com/rodaine/table v1.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.17.1 // indirect
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
|
||||
github.com/showwin/speedtest-go v1.7.7 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/showwin/speedtest-go v1.7.10 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.21.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.8.0 // indirect
|
||||
github.com/tsosunchia/powclient v0.1.5 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/tsosunchia/powclient v0.2.0 // indirect
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/net v0.26.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/mod v0.29.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/sync v0.18.0 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
golang.org/x/term v0.37.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/tools v0.38.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
334
go.sum
334
go.sum
@@ -1,53 +1,86 @@
|
||||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
|
||||
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM=
|
||||
github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg=
|
||||
github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/huin/goupnp v1.2.0 h1:uOKW26NG1hsSSbXIZ1IR7XP9Gjd1U8pnLaCMgntmkmY=
|
||||
github.com/huin/goupnp v1.2.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/imroc/req/v3 v3.43.7 h1:dOcNb9n0X83N5/5/AOkiU+cLhzx8QFXjv5MhikazzQA=
|
||||
github.com/imroc/req/v3 v3.43.7/go.mod h1:SQIz5iYop16MJxbo8ib+4LnostGCok8NQf8ToyQc2xA=
|
||||
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||
github.com/imroc/req/v3 v3.54.0 h1:kwWJSpT7OvjJ/Q8ykp+69Ye5H486RKDcgEoepw1Ren4=
|
||||
github.com/imroc/req/v3 v3.54.0/go.mod h1:P8gCJjG/XNUFeP6WOi40VAXfYwT+uPM00xvoBWiwzUQ=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/jaypipes/ghw v0.17.0 h1:EVLJeNcy5z6GK/Lqby0EhBpynZo+ayl8iJWY0kbEUJA=
|
||||
github.com/jaypipes/ghw v0.17.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
|
||||
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
|
||||
github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jsdelivr/globalping-cli v1.5.1 h1:7RZNmIljSBXe0xBeOoGQHXZNwHo6zDuQ0BI9hF12gLY=
|
||||
github.com/jsdelivr/globalping-cli v1.5.1/go.mod h1:Gw70OWvN6hIt0t4hftyUhcHuJQMTn4CvoobJiaTU0qg=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -58,57 +91,77 @@ github.com/libp2p/go-nat v0.2.0 h1:Tyz+bUFAYqGyJ/ppPPymMGbIgNRH+WqC5QrT5fKrrGk=
|
||||
github.com/libp2p/go-nat v0.2.0/go.mod h1:3MJr+GRpRkyT65EpVPBstXLvOlAPzUVlG6Pwg9ohLJk=
|
||||
github.com/libp2p/go-netroute v0.2.1 h1:V8kVrpD8GK0Riv15/7VN6RbUQ3URNZVosw7H2v9tksU=
|
||||
github.com/libp2p/go-netroute v0.2.1/go.mod h1:hraioZr0fhBjG0ZRXJJ6Zj2IVEVNx6tDTFQfSmcq7mQ=
|
||||
github.com/lionsoul2014/ip2region v2.11.2+incompatible h1:+VRsGcrHz8ewXI/2UzTptJlACsxD/p4xCxuql4u2nKU=
|
||||
github.com/lionsoul2014/ip2region v2.11.2+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs=
|
||||
github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/nxtrace/NTrace-core v1.3.1 h1:f4z5UaZEuhUP/g6xElpZ2bo+guWITJVrMKrJTqd27oc=
|
||||
github.com/nxtrace/NTrace-core v1.3.1/go.mod h1:0Px/Zc60qk6cssmP+yv4kstFxvX9sXqDduoVqBO+qf8=
|
||||
github.com/oneclickvirt/CommonMediaTests v0.0.2-20240630023003 h1:8jQFqDK5m7V9xlLBk8rSUYDyi3u5xzf5kqwEx5b+/7g=
|
||||
github.com/oneclickvirt/CommonMediaTests v0.0.2-20240630023003/go.mod h1:DAmFPRjFV5p9fEzUUSml5jJGn2f1NZJQCzTxITHDjc4=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.10-20240630044930 h1:/Sc4Eas1Pso18ak93wlazkuMKRRElCntITja/0opOpM=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.10-20240630044930/go.mod h1:HP3CvAS+AJWxxY+BVbxIOlvaQ87YOSge89vAMG52b5o=
|
||||
github.com/oneclickvirt/backtrace v0.0.4-20240624090335 h1:0LP5KyA6GLlqQAtOF0uyBSAykHJrzOA96Eb87qUhGQw=
|
||||
github.com/oneclickvirt/backtrace v0.0.4-20240624090335/go.mod h1:zvsC7xY/WZqs5KL2JB967OVnuqjNbxu9bW6wXRLo5h8=
|
||||
github.com/oneclickvirt/basics v0.0.3-20240625075226 h1:K9VriCHIYnXPZXBSn9PRQX+jBS6AIFH8tBVb/i8VGAw=
|
||||
github.com/oneclickvirt/basics v0.0.3-20240625075226/go.mod h1:dTB+/oyFQYfTYX55rFJVWatum5F9g62zjfmHCM6Vj1s=
|
||||
github.com/oneclickvirt/cputest v0.0.6-20240630144058 h1:bFOM4MS+uaU7slFaZR91/bJ57AFM23RWPEik+GTFS2w=
|
||||
github.com/oneclickvirt/cputest v0.0.6-20240630144058/go.mod h1:MmaHN9+XMntI3rLycwj8Ne31fG18IfNoa8N2utDK1CY=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/nxtrace/NTrace-core v1.5.0 h1:n+a/FObw/+CcqvhuSQiWcm1q+ODtfo7Wt3VmaIx504I=
|
||||
github.com/nxtrace/NTrace-core v1.5.0/go.mod h1:/jME48iJ7QaVTzsrTPQyTJ+yExhjeWjax2L6uBd4ckk=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.35-20260207053956 h1:yccGrw/sYOHZMaFJghPVN3Xn6JyTOXsEQc9v0I92k3M=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.35-20260207053956/go.mod h1:oOa6wj/qECtRMxwBO6D7o0L0F0Q/5sQ747OCnFQqoGE=
|
||||
github.com/oneclickvirt/backtrace v0.0.8-20251109090457 h1:599/R/qMAtfPCPG1bPoi6KbjNJzVkKtxm8dvVIdtn5o=
|
||||
github.com/oneclickvirt/backtrace v0.0.8-20251109090457/go.mod h1:mj9TSow7FNszBb3bQj2Hhm41LwBo7HQP6sgaPtovKdM=
|
||||
github.com/oneclickvirt/basics v0.0.16-20251112033526 h1:bgoLaqStV3a6mbPiM++0mYizd278GVa6J6yeIiusV+A=
|
||||
github.com/oneclickvirt/basics v0.0.16-20251112033526/go.mod h1:2PV+1ge01zb0Sqzj2V2I7P0wAdFSLF1XgAiumchJJbg=
|
||||
github.com/oneclickvirt/cputest v0.0.12-20251111095842 h1:ixZUvIkSlsIZfsg+dNDKq/FTofEtUjfA2LtpTrNr/6s=
|
||||
github.com/oneclickvirt/cputest v0.0.12-20251111095842/go.mod h1:vjlH8tkPFft1tlLOpeNskXVvurxkHaJ3+dgFxQGLXY4=
|
||||
github.com/oneclickvirt/dd v0.0.2-20250808062818 h1:0KHrKkdpL5oBE1OHsrRd2siRw4/2k6f9LBaP7T4JpOc=
|
||||
github.com/oneclickvirt/dd v0.0.2-20250808062818/go.mod h1:tImu9sPTkLWo2tf1dEN1xQzrylWKauj9hbU8PHfyAeU=
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446 h1:5Pg3mK/u/vQvSz7anu0nxzrNdELi/AcDAU1mMsmPzyc=
|
||||
github.com/oneclickvirt/defaultset v0.0.2-20240624082446/go.mod h1:e9Jt4tf2sbemCtc84/XgKcHy9EZ2jkc5x2sW1NiJS+E=
|
||||
github.com/oneclickvirt/disktest v0.0.3-20240629152513 h1:ZW7MBMd2HxQi1ktg/ztVI2A10JzF4ZI6I7ATZvcK9w8=
|
||||
github.com/oneclickvirt/disktest v0.0.3-20240629152513/go.mod h1:x7VAJF0Ks05FyE4BI5xedXNKmwbeXfp8GCRALAIlueI=
|
||||
github.com/oneclickvirt/gostun v0.0.2-20240625025941 h1:h+ZL8jkjXR6QE0qEX34FjWTv89+lNj2fEkWx5Agpgzc=
|
||||
github.com/oneclickvirt/gostun v0.0.2-20240625025941/go.mod h1:f7DPEXAxbmwXSW33dbxtb0/KzqvOBWhTs2Or5xBerQA=
|
||||
github.com/oneclickvirt/memorytest v0.0.1-20240624151629 h1:2rJAB3gFGlFPocIb/WRVWYqs4nr2jGYokfDOgqFicD4=
|
||||
github.com/oneclickvirt/memorytest v0.0.1-20240624151629/go.mod h1:+YNzy+NeVg61d0kNwSyVDqHyVtKzjuRe1NvMzsDLg0I=
|
||||
github.com/oneclickvirt/nt3 v0.0.2-20240630152642 h1:BkpR1j9JaDxMzx7iIwtsnFDxSldjYr2TUYpZE7m8z/Y=
|
||||
github.com/oneclickvirt/nt3 v0.0.2-20240630152642/go.mod h1:UojPmtangn17TiQaDccVrZbn6sZwJOtzBgg3idp68cA=
|
||||
github.com/oneclickvirt/portchecker v0.0.1-20240624155429 h1:+wapaOcFrg1iWJDhBKThDzppyIMY7hWxK7F5RBkZg4o=
|
||||
github.com/oneclickvirt/portchecker v0.0.1-20240624155429/go.mod h1:HQxSTrqM8/QFqHMTBZ7S8H9eEO5FkUXU1eb7ZX5Mk+k=
|
||||
github.com/oneclickvirt/security v0.0.2-20240630142358 h1:yioMtHuW9wbJ7payL8GCPnj1ScEqNoqbNHt3+4Z4VAg=
|
||||
github.com/oneclickvirt/security v0.0.2-20240630142358/go.mod h1:cUNNOldoVQuovyc0EtAVAZlAT4y2xA1e0uA80aj1ly8=
|
||||
github.com/oneclickvirt/speedtest v0.0.6-20240630114816 h1:gI2hqV0IwcvGqYzLhlt4w2oZvYhCL+73jftUuJYBsuI=
|
||||
github.com/oneclickvirt/speedtest v0.0.6-20240630114816/go.mod h1:zd5ZgIGslmtQLQehEfRjyumlvgDHTpCSMchKfKXoASI=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
|
||||
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/oneclickvirt/disktest v0.0.10-20250924030424 h1:56Aq2xygO/vA/co5vJ7/MQTNijIDl8eYbVk8uCWN4mI=
|
||||
github.com/oneclickvirt/disktest v0.0.10-20250924030424/go.mod h1:Vp3iMVBD4ccReDJz5n5SlzUdq0kDuVhpRklQk21KT+8=
|
||||
github.com/oneclickvirt/fio v0.0.2-20250808045755 h1:eWihCRWcalJjPIdrF8dMe68ZiPnMkSfHC8ENvElp/xE=
|
||||
github.com/oneclickvirt/fio v0.0.2-20250808045755/go.mod h1:NIq+XYTey68KNERGIy/oRDlzpwLzBVoHOCiqX8didsE=
|
||||
github.com/oneclickvirt/gostun v0.0.5-20250727155022 h1:/e3gSUrOp1tg/1NTRx+P8B51OGcP26Q6//5EoSIjOvk=
|
||||
github.com/oneclickvirt/gostun v0.0.5-20250727155022/go.mod h1:pfp7MFZJK9n/KTLAVqqFcCAns4xqMykmjI+1UeF/vdE=
|
||||
github.com/oneclickvirt/mbw v0.0.1-20250808061222 h1:WGXOe6QvHiDRhPVMI0VcctjzW08kGvJf50yq5YeZCtw=
|
||||
github.com/oneclickvirt/mbw v0.0.1-20250808061222/go.mod h1:0Vq6NRpyLmGUdfHfL3uDcFsuZhi7KlG+OCs5ky2757Y=
|
||||
github.com/oneclickvirt/memorytest v0.0.10-20251218032900 h1:SmRFfPLyGfTVWIgC50lEGgOpbqahtMHIlyOMSbrhj9Y=
|
||||
github.com/oneclickvirt/memorytest v0.0.10-20251218032900/go.mod h1:4kiHsEWkW9r3/1ZcV5xIweU0smiKP0IRfQj74AUIiVI=
|
||||
github.com/oneclickvirt/nt3 v0.0.11-20260112140912 h1:e3tgkEmydsML6ziOdWwsVGwysTRYS82SuWrP0HnIw9g=
|
||||
github.com/oneclickvirt/nt3 v0.0.11-20260112140912/go.mod h1:u/y3sMhyt4wiQlR7yS68CudwjXCa/4V6ozWI7awsCws=
|
||||
github.com/oneclickvirt/pingtest v0.0.9-20251104112920 h1:j3Fjhy0YHT/VF7iuAVVELaRXkquvRd64tWWfFLJs01o=
|
||||
github.com/oneclickvirt/pingtest v0.0.9-20251104112920/go.mod h1:gxwsxxwitNQiGq2OI0ZogYoOLwc8DtuOdSRe6/EvRqs=
|
||||
github.com/oneclickvirt/portchecker v0.0.3-20250728015900 h1:AomzdppSOFB70AJESQhlp0IPbsHTTJGimAWDk2TzCWM=
|
||||
github.com/oneclickvirt/portchecker v0.0.3-20250728015900/go.mod h1:9sjMDPCd4Z40wkYB0S9gQPGH8YPtnNE1ZJthVIuHUzA=
|
||||
github.com/oneclickvirt/privatespeedtest v0.0.1-20260112130218 h1:h2k2fHtrsIIP/x/apEWkQGlTKuIumz8GrUR/df41YhE=
|
||||
github.com/oneclickvirt/privatespeedtest v0.0.1-20260112130218/go.mod h1:IXOlKKX4DUNqxOaW/K9bcdrBiWxo0jGSLXeBeo7NrTo=
|
||||
github.com/oneclickvirt/security v0.0.8-20260202071316 h1:ULZWXC99IzrdFEG05D2/MQklKAhztQNc6UYCE3fEQeU=
|
||||
github.com/oneclickvirt/security v0.0.8-20260202071316/go.mod h1:aPMIwqsz7wiUH1cqvtRr9+QcQRkKzlUWecDM6SGVddc=
|
||||
github.com/oneclickvirt/speedtest v0.0.11-20251102151740 h1:1NUrNt5ay6/xVNC5x62UrQjPqK8jgbKtyjBml/3boZg=
|
||||
github.com/oneclickvirt/speedtest v0.0.11-20251102151740/go.mod h1:fy0II2Wo7kDWVBKTwcHdodZwyfmJo0g8N9V02EwQDZE=
|
||||
github.com/oneclickvirt/stream v0.0.2-20250924154001 h1:GuJWdiPkoK84+y/+oHKr2Ghl3c/MzS9Z5m1nM+lMmy4=
|
||||
github.com/oneclickvirt/stream v0.0.2-20250924154001/go.mod h1:oWaizaHTC2VQciBC9RfaLbAOf8qeR6n20/gY7QxriDE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
@@ -119,42 +172,46 @@ github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA=
|
||||
github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI=
|
||||
github.com/refraction-networking/utls v1.6.6 h1:igFsYBUJPYM8Rno9xUuDoM5GQrVEqY4llzEXOkL43Ig=
|
||||
github.com/refraction-networking/utls v1.6.6/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus-community/pro-bing v0.4.1 h1:aMaJwyifHZO0y+h8+icUz0xbToHbia0wdmzdVZ+Kl3w=
|
||||
github.com/prometheus-community/pro-bing v0.4.1/go.mod h1:aLsw+zqCaDoa2RLVVSX3+UiCkBBXTMtZC3c7EkfWnAE=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/refraction-networking/utls v1.7.3 h1:L0WRhHY7Oq1T0zkdzVZMR6zWZv+sXbHB9zcuvsAEqCo=
|
||||
github.com/refraction-networking/utls v1.7.3/go.mod h1:TUhh27RHMGtQvjQq+RyO11P6ZNQNBb3N0v7wsEjKAIQ=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74=
|
||||
github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
|
||||
github.com/rodaine/table v1.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE=
|
||||
github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dmk4WxU=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
|
||||
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/showwin/speedtest-go v1.7.7 h1:VmK75SZOTKiuWjIVrs+mo7ZoKEw0utiGCvpnurS0olU=
|
||||
github.com/showwin/speedtest-go v1.7.7/go.mod h1:uLgdWCNarXxlYsL2E5TOZpCIwpgSWnEANZp7gfHXHu0=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
|
||||
github.com/showwin/speedtest-go v1.7.10 h1:9o5zb7KsuzZKn+IE2//z5btLKJ870JwO6ETayUkqRFw=
|
||||
github.com/showwin/speedtest-go v1.7.10/go.mod h1:Ei7OCTmNPdWofMadzcfgq1rUO7mvJy9Jycj//G7vyfA=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@@ -164,49 +221,59 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
|
||||
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
|
||||
github.com/tsosunchia/powclient v0.1.5 h1:hpixFWoPbWSEC0zc9osSltyjtr1+SnhCueZVLkEpyyU=
|
||||
github.com/tsosunchia/powclient v0.1.5/go.mod h1:yNlzyq+w9llYZV+0q7nrX83ULy4ghq2mCjpTLJFJ2pg=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI=
|
||||
github.com/tsosunchia/powclient v0.2.0 h1:BDrI3O69CbzarbD+CnnY10Kuwn8xlmtQR0m5tBp+BG8=
|
||||
github.com/tsosunchia/powclient v0.2.0/go.mod h1:fkb7tTW+HMH3ZWZzQUgwvvFKMj/8Ys+C8Sm/uGQzDA0=
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f h1:glX3VZCYwW1/OmFxOjazfCtBLxXB3YNZk9LF2lYx+Lw=
|
||||
github.com/xjasonlyu/windivert-go v0.0.0-20201010013527-4239d0afa76f/go.mod h1:gh//RKyt2Gesx3eOj3ulzrSQ60ySj2UA4qnOdrtarvg=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
@@ -215,65 +282,66 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
|
||||
267
goecs.go
267
goecs.go
@@ -1,187 +1,110 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/ecs/backtrace"
|
||||
"github.com/oneclickvirt/ecs/commediatest"
|
||||
"github.com/oneclickvirt/ecs/cputest"
|
||||
"github.com/oneclickvirt/ecs/disktest"
|
||||
"github.com/oneclickvirt/ecs/memorytest"
|
||||
"github.com/oneclickvirt/ecs/ntrace"
|
||||
"github.com/oneclickvirt/ecs/speedtest"
|
||||
"github.com/oneclickvirt/ecs/unlocktest"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
"github.com/oneclickvirt/portchecker/email"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
unlocktestmodel "github.com/oneclickvirt/UnlockTests/model"
|
||||
backtracemodel "github.com/oneclickvirt/backtrace/model"
|
||||
basicmodel "github.com/oneclickvirt/basics/model"
|
||||
cputestmodel "github.com/oneclickvirt/cputest/model"
|
||||
disktestmodel "github.com/oneclickvirt/disktest/disk"
|
||||
menu "github.com/oneclickvirt/ecs/internal/menu"
|
||||
params "github.com/oneclickvirt/ecs/internal/params"
|
||||
"github.com/oneclickvirt/ecs/internal/runner"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
gostunmodel "github.com/oneclickvirt/gostun/model"
|
||||
memorytestmodel "github.com/oneclickvirt/memorytest/memory"
|
||||
nt3model "github.com/oneclickvirt/nt3/model"
|
||||
ptmodel "github.com/oneclickvirt/pingtest/model"
|
||||
speedtestmodel "github.com/oneclickvirt/speedtest/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ecsVersion = "2024.06.30"
|
||||
showVersion bool
|
||||
language string
|
||||
cpuTestMethod, cpuTestThreadMode string
|
||||
memoryTestMethod string
|
||||
diskTestMethod, diskTestPath string
|
||||
diskMultiCheck bool
|
||||
nt3CheckType, nt3Location string
|
||||
spNum int
|
||||
width = 84
|
||||
ecsVersion = "v0.1.118" // 融合怪版本号
|
||||
configs = params.NewConfig(ecsVersion) // 全局配置实例
|
||||
userSetFlags = make(map[string]bool) // 用于跟踪哪些参数是用户显式设置的
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.BoolVar(&showVersion, "v", false, "Show version information")
|
||||
flag.StringVar(&language, "l", "zh", "Specify language (supported: en, zh)")
|
||||
flag.StringVar(&cpuTestMethod, "cpum", "sysbench", "Specify CPU test method (supported: sysbench, geekbench, winsat)")
|
||||
flag.StringVar(&cpuTestThreadMode, "cput", "multi", "Specify CPU test thread mode (supported: single multi)")
|
||||
flag.StringVar(&memoryTestMethod, "memorym", "dd", "Specify Memory test method (supported: sysbench, dd, winsat)")
|
||||
flag.StringVar(&diskTestMethod, "diskm", "fio", "Specify Disk test method (supported: fio, dd, winsat)")
|
||||
flag.StringVar(&diskTestPath, "diskp", "", "Specify Disk test path, example: -diskp /root")
|
||||
flag.BoolVar(&diskMultiCheck, "diskmc", false, "Enable multiple disk checks, example: -diskmc=false")
|
||||
flag.StringVar(&nt3Location, "nt3loc", "GZ", "指定三网回程路由检测的地址,支持 GZ, SH, BJ, CD 对应 广州,上海,北京,成都")
|
||||
flag.StringVar(&nt3CheckType, "nt3t", "ipv4", "指定三网回程路由检测的类型,支持 both, ipv4, ipv6")
|
||||
flag.IntVar(&spNum, "spnum", 2, "Specify speedtest each operator servers num")
|
||||
flag.Parse()
|
||||
if showVersion {
|
||||
fmt.Println(ecsVersion)
|
||||
return
|
||||
func initLogger() {
|
||||
if configs.EnableLogger {
|
||||
gostunmodel.EnableLoger = true
|
||||
basicmodel.EnableLoger = true
|
||||
cputestmodel.EnableLoger = true
|
||||
memorytestmodel.EnableLoger = true
|
||||
disktestmodel.EnableLoger = true
|
||||
unlocktestmodel.EnableLoger = true
|
||||
ptmodel.EnableLoger = true
|
||||
backtracemodel.EnableLoger = true
|
||||
nt3model.EnableLoger = true
|
||||
speedtestmodel.EnableLoger = true
|
||||
}
|
||||
}
|
||||
|
||||
func handleLanguageSpecificSettings() {
|
||||
if configs.Language == "en" {
|
||||
configs.BacktraceStatus = false
|
||||
configs.Nt3Status = false
|
||||
}
|
||||
if !configs.EnableUpload {
|
||||
configs.SecurityTestStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
configs.ParseFlags(os.Args[1:])
|
||||
if configs.HandleHelpAndVersion("goecs") {
|
||||
return
|
||||
}
|
||||
initLogger()
|
||||
utils.CheckAndFixAndroidDNS(configs.Language)
|
||||
preCheck := utils.CheckPublicAccess(3 * time.Second)
|
||||
go func() {
|
||||
if preCheck.Connected {
|
||||
http.Get("https://hits.spiritlhl.net/goecs.svg?action=hit&title=Hits&title_bg=%23555555&count_bg=%230eecf8&edge_flat=false")
|
||||
}
|
||||
}()
|
||||
if configs.MenuMode {
|
||||
menu.HandleMenuMode(preCheck, configs)
|
||||
} else {
|
||||
configs.OnlyIpInfoCheck = true
|
||||
}
|
||||
handleLanguageSpecificSettings()
|
||||
if !preCheck.Connected {
|
||||
configs.EnableUpload = false
|
||||
}
|
||||
var (
|
||||
wg1, wg2, wg3 sync.WaitGroup
|
||||
basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo string
|
||||
output, tempOutput string
|
||||
outputMutex sync.Mutex
|
||||
infoMutex sync.Mutex // 保护并发字符串写入
|
||||
)
|
||||
startTime := time.Now()
|
||||
uploadDone := make(chan bool, 1)
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
go runner.HandleSignalInterrupt(sig, configs, &startTime, &output, tempOutput, uploadDone, &outputMutex)
|
||||
switch configs.Language {
|
||||
case "zh":
|
||||
runner.RunChineseTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
case "en":
|
||||
runner.RunEnglishTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
default:
|
||||
fmt.Println("Unsupported language")
|
||||
}
|
||||
if preCheck.Connected {
|
||||
runner.HandleUploadResults(configs, output)
|
||||
}
|
||||
configs.Finish = true
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
fmt.Scanln()
|
||||
}
|
||||
startTime := time.Now()
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
basicInfo, securityInfo, emailInfo, mediaInfo string
|
||||
output, tempOutput string
|
||||
)
|
||||
output = utils.PrintAndCapture(func() {
|
||||
switch language {
|
||||
case "zh":
|
||||
utils.PrintHead(language, width, ecsVersion)
|
||||
utils.PrintCenteredTitle("基础信息", width)
|
||||
basicInfo, securityInfo, nt3CheckType = utils.SecurityCheck(language, nt3CheckType)
|
||||
fmt.Printf(basicInfo)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU测试-通过%s测试", cpuTestMethod), width)
|
||||
cputest.CpuTest(language, cpuTestMethod, cpuTestThreadMode)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("内存测试-通过%s测试", cpuTestMethod), width)
|
||||
memorytest.MemoryTest(language, memoryTestMethod)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", diskTestMethod), width)
|
||||
disktest.DiskTest(language, diskTestMethod, diskTestPath, diskMultiCheck)
|
||||
utils.PrintCenteredTitle("御三家流媒体解锁", width)
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
emailInfo = email.EmailCheck()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
mediaInfo = unlocktest.MediaTest(language)
|
||||
}()
|
||||
commediatest.ComMediaTest(language)
|
||||
utils.PrintCenteredTitle("跨国流媒体解锁", width)
|
||||
wg.Wait() // 后台任务含流媒体测试和邮件测试
|
||||
fmt.Printf(mediaInfo)
|
||||
utils.PrintCenteredTitle("IP质量检测", width)
|
||||
fmt.Printf(securityInfo)
|
||||
utils.PrintCenteredTitle("邮件端口检测", width)
|
||||
fmt.Println(emailInfo)
|
||||
if runtime.GOOS != "windows" {
|
||||
utils.PrintCenteredTitle("三网回程", width)
|
||||
backtrace.BackTrace()
|
||||
// nexttrace 在win上不支持检测,报错 bind: An invalid argument was supplied.
|
||||
utils.PrintCenteredTitle("路由检测", width)
|
||||
ntrace.TraceRoute3(language, nt3Location, nt3CheckType)
|
||||
}
|
||||
utils.PrintCenteredTitle("就近节点测速", width)
|
||||
speedtest.ShowHead(language)
|
||||
speedtest.NearbySP()
|
||||
speedtest.CustomSP("net", "global", 4)
|
||||
speedtest.CustomSP("net", "cu", spNum)
|
||||
speedtest.CustomSP("net", "ct", spNum)
|
||||
speedtest.CustomSP("net", "cmcc", spNum)
|
||||
utils.PrintCenteredTitle("", width)
|
||||
endTime := time.Now()
|
||||
duration := endTime.Sub(startTime)
|
||||
minutes := int(duration.Minutes())
|
||||
seconds := int(duration.Seconds()) % 60
|
||||
currentTime := time.Now().Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
fmt.Printf("花费 : %d 分 %d 秒\n", minutes, seconds)
|
||||
fmt.Printf("时间 : %s\n", currentTime)
|
||||
utils.PrintCenteredTitle("", width)
|
||||
case "en":
|
||||
utils.PrintHead(language, width, ecsVersion)
|
||||
utils.PrintCenteredTitle("Basic Information", width)
|
||||
basicInfo, securityInfo, nt3CheckType = utils.SecurityCheck(language, nt3CheckType)
|
||||
fmt.Printf(basicInfo)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU Test - %s Method", cpuTestMethod), width)
|
||||
cputest.CpuTest(language, cpuTestMethod, cpuTestThreadMode)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Memory Test - %s Method", memoryTestMethod), width)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk Test - %s Method", diskTestMethod), width)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk Test - %s Method", diskTestMethod), width)
|
||||
disktest.DiskTest(language, diskTestMethod, diskTestPath, diskMultiCheck)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
emailInfo = email.EmailCheck()
|
||||
}()
|
||||
utils.PrintCenteredTitle("The Three Families Streaming Media Unlock", width)
|
||||
commediatest.ComMediaTest(language)
|
||||
utils.PrintCenteredTitle("Cross-Border Streaming Media Unlock", width)
|
||||
unlocktest.MediaTest(language)
|
||||
utils.PrintCenteredTitle("IP Quality Check", width)
|
||||
fmt.Printf(securityInfo)
|
||||
utils.PrintCenteredTitle("Email Port Check", width)
|
||||
wg.Wait()
|
||||
fmt.Println(emailInfo)
|
||||
//utils.PrintCenteredTitle("Return Path Routing", width)
|
||||
utils.PrintCenteredTitle("Nearby Node Speed Test", width)
|
||||
speedtest.ShowHead(language)
|
||||
speedtest.NearbySP()
|
||||
speedtest.CustomSP("net", "global", -1)
|
||||
utils.PrintCenteredTitle("", width)
|
||||
endTime := time.Now()
|
||||
duration := endTime.Sub(startTime)
|
||||
minutes := int(duration.Minutes())
|
||||
seconds := int(duration.Seconds()) % 60
|
||||
currentTime := time.Now().Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
fmt.Printf("Cost Time : %d 分 %d 秒\n", minutes, seconds)
|
||||
fmt.Printf("Current Time : %s\n", currentTime)
|
||||
utils.PrintCenteredTitle("", width)
|
||||
default:
|
||||
fmt.Println("Unsupported language")
|
||||
}
|
||||
}, tempOutput, output)
|
||||
// 创建文件
|
||||
filePath := "goecs.txt"
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("Can not make file:", err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
// 将 output 写入文件
|
||||
// 匹配 ANSI 转义序列
|
||||
ansiRegex := regexp.MustCompile("\x1B\\[[0-9;]+[a-zA-Z]")
|
||||
// 移除 ANSI 转义序列
|
||||
cleanedOutput := ansiRegex.ReplaceAllString(output, "")
|
||||
_, err = file.WriteString(cleanedOutput)
|
||||
if err != nil {
|
||||
fmt.Println("Can not write file:", err)
|
||||
return
|
||||
}
|
||||
// 获取文件的绝对路径
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("Get file absPath failed:", err)
|
||||
return
|
||||
}
|
||||
shorturl, err := utils.UploadText(absPath)
|
||||
if err != nil {
|
||||
fmt.Println("Upload failed, can not generate short URL.")
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
fmt.Println("Upload successful, short URL:", shorturl)
|
||||
}
|
||||
|
||||
791
goecs.sh
791
goecs.sh
@@ -1,138 +1,301 @@
|
||||
#!/bin/bash
|
||||
#From https://github.com/oneclickvirt/ecs
|
||||
#2024.06.29
|
||||
#!/bin/sh
|
||||
# From https://github.com/oneclickvirt/ecs
|
||||
# 2025.10.08
|
||||
|
||||
# curl -L https://raw.githubusercontent.com/oneclickvirt/ecs/master/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
# 或
|
||||
# curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
|
||||
cat <<"EOF"
|
||||
GGGGGGGG OOOOOOO EEEEEEEE CCCCCCCCC SSSSSSSSSS
|
||||
GG GG OO OO EE CC SS
|
||||
GG OO OO EE CC SS
|
||||
GG OO OO EE CC SS
|
||||
GG OO OO EEEEEEEE CC SSSSSSSSSS
|
||||
GG GGGGGG OO OO EE CC SS
|
||||
GG GG OO OO EE CC SS
|
||||
GG GG OO OO EE CC SS
|
||||
GGGGGGGG OOOOOOO EEEEEEEE CCCCCCCCC SSSSSSSSSS
|
||||
,ad8888ba, ,ad8888ba, 88888888888 ,ad8888ba, ad88888ba
|
||||
d8"' `"8b d8"' `"8b 88 d8"' `"8b d8" "8b
|
||||
d8' d8' `8b 88 d8' Y8a
|
||||
88 88 88 88aaaaa 88 `"Y8aaaaa,
|
||||
88 88888 88 88 88""""" 88 `"""""8b,
|
||||
Y8, 88 Y8, ,8P 88 Y8, `8b
|
||||
Y8a. .a88 Y8a. .a8P 88 Y8a. .a8P Y8a a8P
|
||||
`"Y88888P" `"Y8888Y"' 88888888888 `"Y8888Y"' "Y88888P"
|
||||
EOF
|
||||
|
||||
cd /root >/dev/null 2>&1
|
||||
if [ ! -d "/usr/bin/" ]; then
|
||||
mkdir -p "/usr/bin/"
|
||||
fi
|
||||
_red() { echo -e "\033[31m\033[01m$@\033[0m"; }
|
||||
_green() { echo -e "\033[32m\033[01m$@\033[0m"; }
|
||||
_yellow() { echo -e "\033[33m\033[01m$@\033[0m"; }
|
||||
_blue() { echo -e "\033[36m\033[01m$@\033[0m"; }
|
||||
_red() { printf "\033[31m\033[01m%s\033[0m\n" "$*"; }
|
||||
_green() { printf "\033[32m\033[01m%s\033[0m\n" "$*"; }
|
||||
_yellow() { printf "\033[33m\033[01m%s\033[0m\n" "$*"; }
|
||||
_blue() { printf "\033[36m\033[01m%s\033[0m\n" "$*"; }
|
||||
reading() {
|
||||
printf "\033[32m\033[01m%s\033[0m" "$1"
|
||||
read "$2"
|
||||
}
|
||||
|
||||
check_cdn() {
|
||||
local o_url=$1
|
||||
for cdn_url in "${cdn_urls[@]}"; do
|
||||
if curl -sL -k "$cdn_url$o_url" --max-time 6 | grep -q "success" >/dev/null 2>&1; then
|
||||
export cdn_success_url="$cdn_url"
|
||||
return
|
||||
local o_url="$1"
|
||||
local cdn_url
|
||||
for cdn_url in $cdn_urls; do
|
||||
if curl -4 -sL -k "$cdn_url$o_url" --max-time 6 | grep -q "success" >/dev/null 2>&1; then
|
||||
cdn_success_url="$cdn_url"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
export cdn_success_url=""
|
||||
cdn_success_url=""
|
||||
return 1
|
||||
}
|
||||
|
||||
check_cdn_file() {
|
||||
check_cdn "https://raw.githubusercontent.com/spiritLHLS/ecs/main/back/test"
|
||||
if [ -n "$cdn_success_url" ]; then
|
||||
echo "CDN available, using CDN"
|
||||
_green "CDN available, using CDN"
|
||||
else
|
||||
echo "No CDN available, no use CDN"
|
||||
_yellow "No CDN available, no use CDN"
|
||||
fi
|
||||
}
|
||||
|
||||
download_file() {
|
||||
local url="$1"
|
||||
local output="$2"
|
||||
if ! wget -O "$output" "$url" 2>/dev/null; then
|
||||
_yellow "wget failed, trying curl..."
|
||||
if ! curl -L -o "$output" "$url" 2>/dev/null; then
|
||||
_red "Both wget and curl failed. Unable to download the file."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_china() {
|
||||
_yellow "Detecting IP region......"
|
||||
if [ -z "${CN}" ]; then
|
||||
if curl -m 6 -s https://ipapi.co/json | grep -q 'China'; then
|
||||
_yellow "According to ipapi.co, this IP may be located in China"
|
||||
if [ "$noninteractive" != "true" ]; then
|
||||
reading "Use China mirror for installation? ([y]/n) " input
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY] | "")
|
||||
_green "China mirror selected"
|
||||
CN=true
|
||||
;;
|
||||
[nN][oO] | [nN])
|
||||
_yellow "China mirror not selected"
|
||||
CN=false
|
||||
;;
|
||||
*)
|
||||
_green "China mirror selected"
|
||||
CN=true
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# In non-interactive mode, default to not using China mirror
|
||||
CN=false
|
||||
fi
|
||||
else
|
||||
CN=false
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_memory_size() {
|
||||
if [ -f /proc/meminfo ]; then
|
||||
local mem_kb
|
||||
mem_kb=$(grep MemTotal /proc/meminfo | awk '{print $2}')
|
||||
echo $((mem_kb / 1024)) # Convert to MB
|
||||
return 0
|
||||
fi
|
||||
if command -v free >/dev/null 2>&1; then
|
||||
local mem_kb
|
||||
mem_kb=$(free -m | awk '/^Mem:/ {print $2}')
|
||||
echo "$mem_kb" # Already in MB
|
||||
return 0
|
||||
fi
|
||||
if command -v sysctl >/dev/null 2>&1; then
|
||||
local mem_bytes
|
||||
mem_bytes=$(sysctl -n hw.memsize 2>/dev/null || sysctl -n hw.physmem 2>/dev/null)
|
||||
if [ -n "$mem_bytes" ]; then
|
||||
echo $((mem_bytes / 1024 / 1024)) # Convert to MB
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
echo 0
|
||||
return 1
|
||||
}
|
||||
|
||||
cleanup_epel() {
|
||||
_yellow "Cleaning up EPEL repositories..."
|
||||
rm -f /etc/yum.repos.d/*epel*
|
||||
yum clean all >/dev/null 2>&1
|
||||
}
|
||||
|
||||
goecs_check() {
|
||||
os=$(uname -s)
|
||||
arch=$(uname -m)
|
||||
ECS_VERSION=$(curl -m 6 -sSL "https://api.github.com/repos/oneclickvirt/ecs/releases/latest" | awk -F \" '/tag_name/{gsub(/^v/,"",$4); print $4}')
|
||||
# 如果 https://api.github.com/ 请求失败,则使用 https://githubapi.spiritlhl.workers.dev/ ,此时可能宿主机无IPV4网络
|
||||
if [ -z "$ECS_VERSION" ]; then
|
||||
ECS_VERSION=$(curl -m 6 -sSL "https://githubapi.spiritlhl.workers.dev/repos/oneclickvirt/ecs/releases/latest" | awk -F \" '/tag_name/{gsub(/^v/,"",$4); print $4}')
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
INSTALL_CMD="apt-get -y install"
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
INSTALL_CMD="yum -y install"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
INSTALL_CMD="dnf -y install"
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
INSTALL_CMD="pacman -S --noconfirm"
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
INSTALL_CMD="apk add"
|
||||
elif command -v zypper >/dev/null 2>&1; then
|
||||
INSTALL_CMD="zypper install -y"
|
||||
fi
|
||||
# 如果 https://githubapi.spiritlhl.workers.dev/ 请求失败,则使用 https://githubapi.spiritlhl.top/ ,此时可能宿主机在国内
|
||||
if [ -z "$ECS_VERSION" ]; then
|
||||
ECS_VERSION=$(curl -m 6 -sSL "https://githubapi.spiritlhl.top/repos/oneclickvirt/ecs/releases/latest" | awk -F \" '/tag_name/{gsub(/^v/,"",$4); print $4}')
|
||||
if ! command -v unzip >/dev/null 2>&1; then
|
||||
_green "Installing unzip"
|
||||
${INSTALL_CMD} unzip
|
||||
fi
|
||||
# 检测原始goecs命令是否存在,若存在则升级,不存在则安装
|
||||
version_output=$(goecs -v || ./goecs -v)
|
||||
if [ $? -eq 0 ]; then
|
||||
extracted_version=$(echo "$version_output" | grep -oP '^v\d+(\.\d+)+')
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
_green "Installing curl"
|
||||
${INSTALL_CMD} curl
|
||||
fi
|
||||
os=$(uname -s 2>/dev/null || echo "Unknown")
|
||||
arch=$(uname -m 2>/dev/null || echo "Unknown")
|
||||
check_china
|
||||
ECS_VERSION="0.1.117"
|
||||
for api in \
|
||||
"https://api.github.com/repos/oneclickvirt/ecs/releases/latest" \
|
||||
"https://githubapi.spiritlhl.workers.dev/repos/oneclickvirt/ecs/releases/latest" \
|
||||
"https://githubapi.spiritlhl.top/repos/oneclickvirt/ecs/releases/latest"; do
|
||||
ECS_VERSION=$(curl -m 6 -sSL "$api" | awk -F \" '/tag_name/{gsub(/^v/,"",$4); print $4}')
|
||||
if [ -n "$ECS_VERSION" ]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$ECS_VERSION" ]; then
|
||||
_yellow "Unable to get version info, using default version 0.1.117"
|
||||
ECS_VERSION="0.1.117"
|
||||
fi
|
||||
version_output=""
|
||||
for cmd_path in "goecs" "./goecs" "/usr/bin/goecs" "/usr/local/bin/goecs"; do
|
||||
if command -v "$cmd_path" >/dev/null 2>&1; then
|
||||
version_output=$($cmd_path -v command 2>/dev/null)
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$version_output" ]; then
|
||||
extracted_version=${version_output#*v}
|
||||
extracted_version=${extracted_version#v}
|
||||
if [ -n "$extracted_version" ]; then
|
||||
current_version=$(echo "$extracted_version" | cut -c 2-)
|
||||
ecs_version=$ECS_VERSION
|
||||
if [[ "$(echo -e "$current_version\n$ecs_version" | sort -V | tail -n 1)" == "$current_version" ]]; then
|
||||
echo "goecs version ($current_version) is latest, no need to upgrade."
|
||||
return
|
||||
if [ "$(printf '%s\n%s\n' "$extracted_version" "$ecs_version" | sort -V | tail -n 1)" = "$extracted_version" ]; then
|
||||
_green "goecs version ($extracted_version) is up to date, no upgrade needed"
|
||||
return 0
|
||||
else
|
||||
echo "goecs version ($current_version) < $ecs_version, need to upgrade, 5 seconds later will start to upgrade"
|
||||
rm -rf /usr/bin/goecs
|
||||
rm -rf goecs
|
||||
_yellow "goecs version ($extracted_version) < $ecs_version, upgrade needed, starting in 5 seconds"
|
||||
rm -rf /usr/bin/goecs /usr/local/bin/goecs ./goecs
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Can not find goecs, need to download and install, 5 seconds later will start to install"
|
||||
_green "goecs not found, installation needed, starting in 5 seconds"
|
||||
fi
|
||||
sleep 5
|
||||
cdn_urls=("https://cdn0.spiritlhl.top/" "http://cdn3.spiritlhl.net/" "http://cdn1.spiritlhl.net/" "http://cdn2.spiritlhl.net/")
|
||||
check_cdn_file
|
||||
if [ "$CN" = "true" ]; then
|
||||
_yellow "Using China mirror for download..."
|
||||
base_url="https://cnb.cool/oneclickvirt/ecs/-/git/raw/main"
|
||||
else
|
||||
cdn_urls="https://cdn0.spiritlhl.top/ http://cdn3.spiritlhl.net/ http://cdn1.spiritlhl.net/ http://cdn2.spiritlhl.net/"
|
||||
check_cdn_file
|
||||
if [ -n "$cdn_success_url" ]; then
|
||||
base_url="${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}"
|
||||
else
|
||||
base_url="https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}"
|
||||
fi
|
||||
fi
|
||||
local zip_file=""
|
||||
case $os in
|
||||
Linux)
|
||||
case $arch in
|
||||
"x86_64" | "x86" | "amd64" | "x64")
|
||||
wget -O goecs.tar.gz "${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}/ecs_${ECS_VERSION}_linux_amd64.tar.gz"
|
||||
Linux|linux|LINUX)
|
||||
case $arch in
|
||||
x86_64|amd64|x64) zip_file="goecs_linux_amd64.zip" ;;
|
||||
i386|i686) zip_file="goecs_linux_386.zip" ;;
|
||||
aarch64|arm64|armv8|armv8l) zip_file="goecs_linux_arm64.zip" ;;
|
||||
arm|armv7l) zip_file="goecs_linux_arm.zip" ;;
|
||||
mips) zip_file="goecs_linux_mips.zip" ;;
|
||||
mipsle) zip_file="goecs_linux_mipsle.zip" ;;
|
||||
s390x) zip_file="goecs_linux_s390x.zip" ;;
|
||||
riscv64) zip_file="goecs_linux_riscv64.zip" ;;
|
||||
*) zip_file="goecs_linux_amd64.zip" ;;
|
||||
esac
|
||||
;;
|
||||
"i386" | "i686")
|
||||
wget -O goecs.tar.gz "${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}/ecs_${ECS_VERSION}_linux_386.tar.gz"
|
||||
FreeBSD|freebsd)
|
||||
case $arch in
|
||||
x86_64|amd64) zip_file="goecs_freebsd_amd64.zip" ;;
|
||||
i386|i686) zip_file="goecs_freebsd_386.zip" ;;
|
||||
arm64|aarch64) zip_file="goecs_freebsd_arm64.zip" ;;
|
||||
*) zip_file="goecs_freebsd_amd64.zip" ;;
|
||||
esac
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O goecs.tar.gz "${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}/ecs_${ECS_VERSION}_linux_arm64.tar.gz"
|
||||
Darwin|darwin)
|
||||
case $arch in
|
||||
x86_64|amd64) zip_file="goecs_darwin_amd64.zip" ;;
|
||||
arm64|aarch64) zip_file="goecs_darwin_arm64.zip" ;;
|
||||
*) zip_file="goecs_darwin_amd64.zip" ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
exit 1
|
||||
_yellow "Unknown system $os, trying amd64 version"
|
||||
zip_file="goecs_linux_amd64.zip"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
Darwin)
|
||||
case $arch in
|
||||
"x86_64" | "x86" | "amd64" | "x64")
|
||||
wget -O goecs.tar.gz "${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}/ecs_${ECS_VERSION}_linux_amd64.tar.gz"
|
||||
;;
|
||||
"armv7l" | "armv8" | "armv8l" | "aarch64" | "arm64")
|
||||
wget -O goecs.tar.gz "${cdn_success_url}https://github.com/oneclickvirt/ecs/releases/download/v${ECS_VERSION}/ecs_${ECS_VERSION}_linux_arm64.tar.gz"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported architecture: $arch"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported operating system: $os"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
tar -xvf goecs.tar.gz
|
||||
rm -rf goecs.tar.gz
|
||||
rm -rf README.md
|
||||
rm -rf LICENSE
|
||||
mv ecs goecs
|
||||
sleep 1
|
||||
download_url="${base_url}/${zip_file}"
|
||||
_green "Downloading $download_url"
|
||||
local max_retries=3
|
||||
local retry_count=0
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
if download_file "$download_url" "goecs.zip"; then
|
||||
break
|
||||
fi
|
||||
_yellow "Download failed, retrying (${retry_count}/${max_retries})..."
|
||||
retry_count=$((retry_count + 1))
|
||||
sleep 2
|
||||
done
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
_red "Download failed, please check your network connection or download manually"
|
||||
return 1
|
||||
fi
|
||||
if ! unzip -o goecs.zip >/dev/null 2>&1; then
|
||||
_red "Extraction failed"
|
||||
return 1
|
||||
fi
|
||||
rm -f goecs.zip README.md LICENSE README_EN.md
|
||||
chmod 777 goecs
|
||||
rm -rf /usr/bin/goecs
|
||||
sleep 1
|
||||
cp goecs /usr/bin/goecs
|
||||
echo "goecs version:"
|
||||
goecs -v || ./goecs -v
|
||||
installed_to_system=false
|
||||
for install_path in "/usr/bin" "/usr/local/bin"; do
|
||||
if [ -d "$install_path" ]; then
|
||||
if cp -f goecs "$install_path/" 2>/dev/null; then
|
||||
installed_to_system=true
|
||||
break
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ "$installed_to_system" = "false" ]; then
|
||||
_yellow "Insufficient permissions to install to system path, goecs is kept in the current directory"
|
||||
_yellow "Please use the following command to run: ./goecs"
|
||||
fi
|
||||
if [ "$os" != "Darwin" ]; then
|
||||
PARAM="net.ipv4.ping_group_range"
|
||||
NEW_VALUE="0 2147483647"
|
||||
if [ -f /etc/sysctl.conf ]; then
|
||||
if grep -q "^$PARAM" /etc/sysctl.conf 2>/dev/null; then
|
||||
sed -i "s/^$PARAM.*/$PARAM = $NEW_VALUE/" /etc/sysctl.conf 2>/dev/null || true
|
||||
else
|
||||
echo "$PARAM = $NEW_VALUE" >> /etc/sysctl.conf 2>/dev/null || true
|
||||
fi
|
||||
sysctl -p >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
setcap cap_net_raw=+ep goecs 2>/dev/null || true
|
||||
setcap cap_net_raw=+ep /usr/bin/goecs 2>/dev/null || true
|
||||
setcap cap_net_raw=+ep /usr/local/bin/goecs 2>/dev/null || true
|
||||
_green "goecs installation complete, current version:"
|
||||
goecs -v 2>/dev/null || ./goecs -v
|
||||
}
|
||||
|
||||
InstallSysbench() {
|
||||
if [ -f "/etc/centos-release" ]; then # CentOS
|
||||
if [ -f "/etc/opencloudos-release" ]; then # OpenCloudOS
|
||||
Var_OSRelease="opencloudos"
|
||||
elif [ -f "/etc/centos-release" ]; then # CentOS
|
||||
Var_OSRelease="centos"
|
||||
elif [ -f "/etc/fedora-release" ]; then # Fedora
|
||||
Var_OSRelease="fedora"
|
||||
@@ -148,7 +311,6 @@ InstallSysbench() {
|
||||
Var_OSRelease="alpinelinux"
|
||||
elif [ -f "/etc/almalinux-release" ]; then # almalinux
|
||||
Var_OSRelease="almalinux"
|
||||
# rockylinux
|
||||
elif [ -f "/etc/arch-release" ]; then # archlinux
|
||||
Var_OSRelease="arch"
|
||||
elif [ -f "/etc/freebsd-update.conf" ]; then # freebsd
|
||||
@@ -156,152 +318,358 @@ InstallSysbench() {
|
||||
else
|
||||
Var_OSRelease="unknown" # 未知系统分支
|
||||
fi
|
||||
case "$Var_OSRelease" in
|
||||
ubuntu | debian | astra) ! apt-get install -y sysbench && apt-get --fix-broken install -y && apt-get install --no-install-recommends -y sysbench ;;
|
||||
centos | rhel | almalinux | redhat) (yum -y install epel-release && yum -y install sysbench) || (dnf install epel-release -y && dnf install sysbench -y) ;;
|
||||
fedora) dnf -y install sysbench ;;
|
||||
arch) pacman -S --needed --noconfirm sysbench && pacman -S --needed --noconfirm libaio && ldconfig ;;
|
||||
freebsd) pkg install -y sysbench ;;
|
||||
alpinelinux) echo -e "${Msg_Warning}Sysbench Module not found, installing ..." && echo -e "${Msg_Warning}SysBench Current not support Alpine Linux, Skipping..." && Var_Skip_SysBench="1" ;;
|
||||
*) echo "Error: Unknown OS release: $os_release" && exit 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
Check_SysBench() {
|
||||
if [ ! -f "/usr/bin/sysbench" ] && [ ! -f "/usr/local/bin/sysbench" ]; then
|
||||
InstallSysbench
|
||||
fi
|
||||
# 尝试编译安装
|
||||
if [ ! -f "/usr/bin/sysbench" ] && [ ! -f "/usr/local/bin/sysbench" ]; then
|
||||
echo -e "${Msg_Warning}Sysbench Module install Failure, trying compile modules ..."
|
||||
Check_Sysbench_InstantBuild
|
||||
fi
|
||||
source ~/.bashrc
|
||||
# 最终检测
|
||||
if [ "$(command -v sysbench)" ] || [ -f "/usr/bin/sysbench" ] || [ -f "/usr/local/bin/sysbench" ]; then
|
||||
_yellow "Install sysbench successfully!"
|
||||
else
|
||||
_red "SysBench Moudle install Failure! Try Restart Bench or Manually install it! (/usr/bin/sysbench)"
|
||||
_blue "Will try to test with geekbench5 instead later on"
|
||||
test_cpu_type="gb5"
|
||||
fi
|
||||
sleep 3
|
||||
}
|
||||
|
||||
Check_Sysbench_InstantBuild() {
|
||||
if [ "${Var_OSRelease}" = "centos" ] || [ "${Var_OSRelease}" = "rhel" ] || [ "${Var_OSRelease}" = "almalinux" ] || [ "${Var_OSRelease}" = "ubuntu" ] || [ "${Var_OSRelease}" = "debian" ] || [ "${Var_OSRelease}" = "fedora" ] || [ "${Var_OSRelease}" = "arch" ] || [ "${Var_OSRelease}" = "astra" ]; then
|
||||
local os_sysbench=${Var_OSRelease}
|
||||
if [ "$os_sysbench" = "astra" ]; then
|
||||
os_sysbench="debian"
|
||||
local mem_size
|
||||
mem_size=$(get_memory_size)
|
||||
if [ -z "$mem_size" ] || [ "$mem_size" -eq 0 ]; then
|
||||
echo "Error: Unable to determine memory size or memory size is zero."
|
||||
elif [ "$mem_size" -lt 1024 ]; then
|
||||
_red "Warning: Your system has less than 1GB RAM (${mem_size}MB)"
|
||||
if [ "$noninteractive" != "true" ]; then
|
||||
reading "Do you want to continue with EPEL installation? (y/N): " confirm
|
||||
case "$confirm" in
|
||||
[Yy]*)
|
||||
;;
|
||||
*)
|
||||
_yellow "Skipping EPEL installation"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
echo -e "${Msg_Info}Release Detected: ${os_sysbench}"
|
||||
echo -e "${Msg_Info}Preparing compile enviorment ..."
|
||||
prepare_compile_env "${os_sysbench}"
|
||||
echo -e "${Msg_Info}Downloading Source code (Version 1.0.20)..."
|
||||
mkdir -p /tmp/sysbench_install/src/
|
||||
mv /tmp/sysbench-1.0.20 /tmp/sysbench_install/src/
|
||||
echo -e "${Msg_Info}Compiling Sysbench Module ..."
|
||||
cd /tmp/sysbench_install/src/sysbench-1.0.20
|
||||
./autogen.sh && ./configure --without-mysql && make -j8 && make install
|
||||
echo -e "${Msg_Info}Cleaning up ..."
|
||||
cd /tmp && rm -rf /tmp/sysbench_install/src/sysbench*
|
||||
else
|
||||
echo -e "${Msg_Warning}Unsupported operating system: ${Var_OSRelease}"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_compile_env() {
|
||||
local system="$1"
|
||||
if [ "${system}" = "centos" ] || [ "${system}" = "rhel" ] || [ "${system}" = "almalinux" ]; then
|
||||
yum install -y epel-release
|
||||
yum install -y wget curl make gcc gcc-c++ make automake libtool pkgconfig libaio-devel
|
||||
elif [ "${system}" = "ubuntu" ] || [ "${system}" = "debian" ]; then
|
||||
! apt-get update && apt-get --fix-broken install -y && apt-get update
|
||||
! apt-get -y install --no-install-recommends curl wget make automake libtool pkg-config libaio-dev unzip && apt-get --fix-broken install -y && apt-get -y install --no-install-recommends curl wget make automake libtool pkg-config libaio-dev unzip
|
||||
elif [ "${system}" = "fedora" ]; then
|
||||
dnf install -y wget curl gcc gcc-c++ make automake libtool pkgconfig libaio-devel
|
||||
elif [ "${system}" = "arch" ]; then
|
||||
pacman -S --needed --noconfirm wget curl gcc gcc make automake libtool pkgconfig libaio lib32-libaio
|
||||
else
|
||||
echo -e "${Msg_Warning}Unsupported operating system: ${system}"
|
||||
case "$Var_OSRelease" in
|
||||
ubuntu | debian | astra)
|
||||
if ! apt-get install -y sysbench; then
|
||||
apt-get --fix-broken install -y
|
||||
apt-get install --no-install-recommends -y sysbench
|
||||
fi
|
||||
;;
|
||||
centos | rhel | almalinux | redhat | opencloudos)
|
||||
if ! yum -y install epel-release || ! yum -y install sysbench; then
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
dnf install epel-release -y
|
||||
dnf install sysbench -y
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
fedora)
|
||||
dnf -y install sysbench ;;
|
||||
arch)
|
||||
pacman -S --needed --noconfirm sysbench
|
||||
pacman -S --needed --noconfirm libaio
|
||||
ldconfig
|
||||
;;
|
||||
freebsd)
|
||||
pkg install -y sysbench ;;
|
||||
alpinelinux)
|
||||
if [ "$noninteractive" != "true" ]; then
|
||||
reading "Do you want to continue with sysbench installation? (y/N): " confirm
|
||||
case "$confirm" in
|
||||
[Yy]*)
|
||||
;;
|
||||
*)
|
||||
_yellow "Skipping sysbench installation"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
ALPINE_VERSION=$(grep -o '^[0-9]\+\.[0-9]\+' /etc/alpine-release)
|
||||
COMMUNITY_REPO="http://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/community"
|
||||
if ! grep -q "^${COMMUNITY_REPO}" /etc/apk/repositories; then
|
||||
echo "Enabling community repository..."
|
||||
echo "${COMMUNITY_REPO}" >> /etc/apk/repositories
|
||||
echo "Community repository has been added."
|
||||
echo "Updating apk package index..."
|
||||
apk update && echo "Package index updated successfully."
|
||||
else
|
||||
echo "Community repository is already enabled."
|
||||
fi
|
||||
if apk info sysbench >/dev/null 2>&1; then
|
||||
echo "Sysbench already installed."
|
||||
else
|
||||
if ! apk add --no-cache sysbench; then
|
||||
echo "Sysbench Module not found, installing ..."
|
||||
echo "SysBench Current not support Alpine Linux, Skipping..."
|
||||
Var_Skip_SysBench="1"
|
||||
else
|
||||
echo "Sysbench installed successfully."
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
_red "Sysbench Install Error: Unknown OS release: $Var_OSRelease" ;;
|
||||
esac
|
||||
case "$SYSTEM" in
|
||||
CentOS|RHEL|AlmaLinux)
|
||||
_yellow "Installing EPEL repository..."
|
||||
if ! yum -y install epel-release; then
|
||||
_red "EPEL installation failed!"
|
||||
cleanup_epel
|
||||
_yellow "Attempting to continue without EPEL..."
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
env_check() {
|
||||
REGEX=("debian|astra" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "'amazon linux'" "fedora" "arch" "freebsd" "alpine" "openbsd")
|
||||
RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Fedora" "Arch" "FreeBSD" "Alpine" "OpenBSD")
|
||||
PACKAGE_UPDATE=("! apt-get update && apt-get --fix-broken install -y && apt-get update" "apt-get update" "yum -y update" "yum -y update" "yum -y update" "pacman -Sy" "pkg update" "apk update" "pkg_add -u")
|
||||
PACKAGE_INSTALL=("apt-get -y install" "apt-get -y install" "yum -y install" "yum -y install" "yum -y install" "pacman -Sy --noconfirm --needed" "pkg install -y" "apk add")
|
||||
PACKAGE_REMOVE=("apt-get -y remove" "apt-get -y remove" "yum -y remove" "yum -y remove" "yum -y remove" "pacman -Rsc --noconfirm" "pkg delete" "apk del")
|
||||
PACKAGE_UNINSTALL=("apt-get -y autoremove" "apt-get -y autoremove" "yum -y autoremove" "yum -y autoremove" "yum -y autoremove" "" "pkg autoremove" "apk autoremove")
|
||||
CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(hostnamectl 2>/dev/null | grep -i system | cut -d : -f2)" "$(lsb_release -sd 2>/dev/null)" "$(grep -i description /etc/lsb-release 2>/dev/null | cut -d \" -f2)" "$(grep . /etc/redhat-release 2>/dev/null)" "$(grep . /etc/issue 2>/dev/null | cut -d \\ -f1 | sed '/^[ ]*$/d')" "$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(uname -s)" "$(uname -s)")
|
||||
SYS="${CMD[0]}"
|
||||
[[ -n $SYS ]] || exit 1
|
||||
for ((int = 0; int < ${#REGEX[@]}; int++)); do
|
||||
if [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]]; then
|
||||
SYSTEM="${RELEASE[int]}"
|
||||
[[ -n $SYSTEM ]] && break
|
||||
# 检测是否为 macOS 系统
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
_green "Detected macOS system"
|
||||
_green "macOS has built-in tools, skipping dependency installation"
|
||||
_green "Environment preparation complete."
|
||||
_green "Next command is: ./goecs.sh install"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f /etc/opencloudos-release ]; then
|
||||
SYS="opencloudos"
|
||||
elif [ -s /etc/os-release ]; then
|
||||
SYS="$(grep -i pretty_name /etc/os-release | cut -d \" -f2)"
|
||||
elif command -v hostnamectl >/dev/null 2>&1; then
|
||||
SYS="$(hostnamectl | grep -i system | cut -d : -f2 | sed 's/^ *//')"
|
||||
elif command -v lsb_release >/dev/null 2>&1; then
|
||||
SYS="$(lsb_release -sd)"
|
||||
elif [ -s /etc/lsb-release ]; then
|
||||
SYS="$(grep -i description /etc/lsb-release | cut -d \" -f2)"
|
||||
elif [ -s /etc/redhat-release ]; then
|
||||
SYS="$(cat /etc/redhat-release)"
|
||||
elif [ -s /etc/issue ]; then
|
||||
SYS="$(head -n1 /etc/issue | cut -d '\' -f1 | sed '/^[ ]*$/d')"
|
||||
else
|
||||
SYS="$(uname -s)"
|
||||
fi
|
||||
SYSTEM=""
|
||||
sys_lower=$(echo "$SYS" | tr '[:upper:]' '[:lower:]')
|
||||
if echo "$sys_lower" | grep -E "debian|astra" >/dev/null 2>&1; then
|
||||
SYSTEM="Debian"
|
||||
UPDATE_CMD="apt-get update"
|
||||
INSTALL_CMD="apt-get -y install"
|
||||
REMOVE_CMD="apt-get -y remove"
|
||||
UNINSTALL_CMD="apt-get -y autoremove"
|
||||
elif echo "$sys_lower" | grep -E "ubuntu" >/dev/null 2>&1; then
|
||||
SYSTEM="Ubuntu"
|
||||
UPDATE_CMD="apt-get update"
|
||||
INSTALL_CMD="apt-get -y install"
|
||||
REMOVE_CMD="apt-get -y remove"
|
||||
UNINSTALL_CMD="apt-get -y autoremove"
|
||||
elif echo "$sys_lower" | grep -E "centos|red hat|kernel|oracle linux|alma|rocky" >/dev/null 2>&1; then
|
||||
SYSTEM="CentOS"
|
||||
UPDATE_CMD="yum -y update"
|
||||
INSTALL_CMD="yum -y install"
|
||||
REMOVE_CMD="yum -y remove"
|
||||
UNINSTALL_CMD="yum -y autoremove"
|
||||
elif echo "$sys_lower" | grep -E "amazon linux" >/dev/null 2>&1; then
|
||||
SYSTEM="CentOS"
|
||||
UPDATE_CMD="yum -y update"
|
||||
INSTALL_CMD="yum -y install"
|
||||
REMOVE_CMD="yum -y remove"
|
||||
UNINSTALL_CMD="yum -y autoremove"
|
||||
elif echo "$sys_lower" | grep -E "fedora" >/dev/null 2>&1; then
|
||||
SYSTEM="Fedora"
|
||||
UPDATE_CMD="yum -y update"
|
||||
INSTALL_CMD="yum -y install"
|
||||
REMOVE_CMD="yum -y remove"
|
||||
UNINSTALL_CMD="yum -y autoremove"
|
||||
elif echo "$sys_lower" | grep -E "arch" >/dev/null 2>&1; then
|
||||
SYSTEM="Arch"
|
||||
UPDATE_CMD="pacman -Sy"
|
||||
INSTALL_CMD="pacman -Sy --noconfirm --needed"
|
||||
REMOVE_CMD="pacman -Rsc --noconfirm"
|
||||
UNINSTALL_CMD="pacman -Rns --noconfirm"
|
||||
elif echo "$sys_lower" | grep -E "freebsd" >/dev/null 2>&1; then
|
||||
SYSTEM="FreeBSD"
|
||||
UPDATE_CMD="pkg update"
|
||||
INSTALL_CMD="pkg install -y"
|
||||
REMOVE_CMD="pkg delete"
|
||||
UNINSTALL_CMD="pkg autoremove"
|
||||
elif echo "$sys_lower" | grep -E "alpine" >/dev/null 2>&1; then
|
||||
SYSTEM="Alpine"
|
||||
UPDATE_CMD="apk update"
|
||||
INSTALL_CMD="apk add --no-cache"
|
||||
REMOVE_CMD="apk del"
|
||||
UNINSTALL_CMD="apk autoremove"
|
||||
elif echo "$sys_lower" | grep -E "openbsd" >/dev/null 2>&1; then
|
||||
SYSTEM="OpenBSD"
|
||||
UPDATE_CMD="pkg_add -qu"
|
||||
INSTALL_CMD="pkg_add -I"
|
||||
REMOVE_CMD="pkg_delete -I"
|
||||
UNINSTALL_CMD="pkg_delete -a"
|
||||
elif echo "$sys_lower" | grep -E "opencloudos" >/dev/null 2>&1; then
|
||||
SYSTEM="OpenCloudOS"
|
||||
UPDATE_CMD="yum -y update"
|
||||
INSTALL_CMD="yum -y install"
|
||||
REMOVE_CMD="yum -y remove"
|
||||
UNINSTALL_CMD="yum -y autoremove"
|
||||
fi
|
||||
if [ -z "$SYSTEM" ]; then
|
||||
_yellow "Unable to recognize system, trying common package managers..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-Debian"
|
||||
UPDATE_CMD="apt-get update"
|
||||
INSTALL_CMD="apt-get -y install"
|
||||
REMOVE_CMD="apt-get -y remove"
|
||||
UNINSTALL_CMD="apt-get -y autoremove"
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-RHEL"
|
||||
UPDATE_CMD="yum -y update"
|
||||
INSTALL_CMD="yum -y install"
|
||||
REMOVE_CMD="yum -y remove"
|
||||
UNINSTALL_CMD="yum -y autoremove"
|
||||
elif command -v dnf >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-Fedora"
|
||||
UPDATE_CMD="dnf -y update"
|
||||
INSTALL_CMD="dnf -y install"
|
||||
REMOVE_CMD="dnf -y remove"
|
||||
UNINSTALL_CMD="dnf -y autoremove"
|
||||
elif command -v pacman >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-Arch"
|
||||
UPDATE_CMD="pacman -Sy"
|
||||
INSTALL_CMD="pacman -S --noconfirm"
|
||||
REMOVE_CMD="pacman -R --noconfirm"
|
||||
UNINSTALL_CMD="pacman -Rns --noconfirm"
|
||||
elif command -v apk >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-Alpine"
|
||||
UPDATE_CMD="apk update"
|
||||
INSTALL_CMD="apk add"
|
||||
REMOVE_CMD="apk del"
|
||||
UNINSTALL_CMD="apk del"
|
||||
elif command -v zypper >/dev/null 2>&1; then
|
||||
SYSTEM="Unknown-SLES"
|
||||
UPDATE_CMD="zypper refresh"
|
||||
INSTALL_CMD="zypper install -y"
|
||||
REMOVE_CMD="zypper remove -y"
|
||||
UNINSTALL_CMD="zypper remove -y"
|
||||
else
|
||||
_red "Unable to recognize package manager, exiting installation"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
_green "System information: $SYSTEM"
|
||||
_green "Update command: $UPDATE_CMD"
|
||||
_green "Install command: $INSTALL_CMD"
|
||||
cdn_urls="https://cdn0.spiritlhl.top/ http://cdn3.spiritlhl.net/ http://cdn1.spiritlhl.net/ http://cdn2.spiritlhl.net/"
|
||||
check_cdn_file
|
||||
_yellow "Warning: System update will be performed"
|
||||
_yellow "This operation may:"
|
||||
_yellow "1. Take considerable time"
|
||||
_yellow "2. Cause temporary network interruptions"
|
||||
_yellow "3. Impact system stability"
|
||||
_yellow "4. Affect subsequent system startups"
|
||||
if [ "$noninteractive" != "true" ]; then
|
||||
reading "Continue with system update? (y/N): " update_confirm
|
||||
case "$update_confirm" in
|
||||
[Yy]*)
|
||||
_green "Updating system package manager..."
|
||||
if ! ${UPDATE_CMD} 2>/dev/null; then
|
||||
_red "System update failed!"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
_yellow "Skipping system update"
|
||||
_yellow "Note: Some packages may fail to install"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
for cmd in sudo wget tar unzip iproute2 systemd-detect-virt dd fio; do
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
_green "Installing $cmd"
|
||||
${INSTALL_CMD} "$cmd"
|
||||
fi
|
||||
done
|
||||
cdn_urls=("https://cdn0.spiritlhl.top/" "http://cdn3.spiritlhl.net/" "http://cdn1.spiritlhl.net/" "http://cdn2.spiritlhl.net/")
|
||||
check_cdn_file
|
||||
if ! command -v dd >/dev/null 2>&1; then
|
||||
_green "Installing dd"
|
||||
$PACKAGE_INSTALL dd
|
||||
fi
|
||||
if ! command -v fio >/dev/null 2>&1; then
|
||||
_green "Installing fio"
|
||||
$PACKAGE_INSTALL fio
|
||||
fi
|
||||
if ! command -v sysbench >/dev/null 2>&1; then
|
||||
_green "Installing sysbench"
|
||||
$PACKAGE_INSTALL sysbench
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Unable to download sysbench through the system's package manager, speak to try compiling and installing it..."
|
||||
if ! wget -O /tmp/sysbench.zip "${cdn_success_url}https://github.com/akopytov/sysbench/archive/1.0.20.zip"; then
|
||||
echo "wget failed, trying with curl"
|
||||
curl -Lk -o /tmp/sysbench.zip "${cdn_success_url}https://github.com/akopytov/sysbench/archive/1.0.20.zip"
|
||||
fi
|
||||
if [ ! -f /tmp/sysbench.zip ]; then
|
||||
wget -q -O /tmp/sysbench.zip "https://hub.fgit.cf/akopytov/sysbench/archive/1.0.20.zip"
|
||||
fi
|
||||
chmod +x /tmp/sysbench.zip
|
||||
unzip /tmp/sysbench.zip -d /tmp
|
||||
if ! ${INSTALL_CMD} sysbench; then
|
||||
_red "Unable to install sysbench through package manager"
|
||||
_yellow "Sysbench installation skipped"
|
||||
fi
|
||||
fi
|
||||
if ! command -v geekbench >/dev/null 2>&1; then
|
||||
_green "Installing geekbench"
|
||||
curl -L "${cdn_success_url}https://raw.githubusercontent.com/oneclickvirt/cputest/main/dgb.sh" -o dgb.sh && chmod +x dgb.sh
|
||||
bash dgb.sh -v gb5
|
||||
_blue "If you not want to use geekbench5, you can use"
|
||||
echo "bash dgb.sh -v gb6"
|
||||
echo "bash dgb.sh -v gb4"
|
||||
_blue "to change version, or use"
|
||||
echo "rm -rf /usr/bin/geekbench*"
|
||||
_blue "to uninstall geekbench"
|
||||
sh dgb.sh -v gb5
|
||||
rm -rf dgb.sh
|
||||
fi
|
||||
if ! command -v speedtest >/dev/null 2>&1; then
|
||||
_green "Installing geekbench"
|
||||
_green "Installing speedtest"
|
||||
curl -L "${cdn_success_url}https://raw.githubusercontent.com/oneclickvirt/speedtest/main/dspt.sh" -o dspt.sh && chmod +x dspt.sh
|
||||
bash dspt.sh
|
||||
sh dspt.sh
|
||||
rm -rf dspt.sh
|
||||
_blue "if you want to use golang origin speedtest, you can use"
|
||||
echo "rm -rf /usr/bin/speedtest"
|
||||
echo "rm -rf /usr/bin/speedtest-go"
|
||||
_blue "to uninstall speedtest and speedtest-go"
|
||||
rm -rf speedtest.tar.gz
|
||||
fi
|
||||
if ! command -v ping >/dev/null 2>&1; then
|
||||
_green "Installing ping"
|
||||
${INSTALL_CMD} iputils-ping >/dev/null 2>&1 || ${INSTALL_CMD} ping >/dev/null 2>&1
|
||||
fi
|
||||
if ! grep -q "^net.ipv4.ping_group_range = 0 2147483647$" /etc/sysctl.conf 2>/dev/null; then
|
||||
echo "net.ipv4.ping_group_range = 0 2147483647" >> /etc/sysctl.conf 2>/dev/null
|
||||
sysctl -p >/dev/null 2>&1
|
||||
fi
|
||||
_green "Environment preparation complete."
|
||||
_green "Next command is: ./goecs.sh install"
|
||||
}
|
||||
|
||||
uninstall_goecs() {
|
||||
rm -rf /root/goecs
|
||||
rm -rf /usr/bin/goecs
|
||||
_green "The command (goecs) has been uninstalled."
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat <<"EOF"
|
||||
可用命令:
|
||||
|
||||
./goecs.sh env 检查并安装依赖包
|
||||
注意: macOS系统会自动跳过依赖安装
|
||||
警告: 此命令会执行系统更新(可选择),可能:
|
||||
1. 耗时较长
|
||||
2. 导致网络短暂中断
|
||||
3. 影响系统稳定性
|
||||
4. 影响后续系统启动
|
||||
对于内存小于1GB的系统,还可能导致:
|
||||
1. 系统卡死
|
||||
2. SSH连接中断
|
||||
3. 关键服务失败
|
||||
推荐:
|
||||
环境依赖安装过程中挂起执行
|
||||
|
||||
可选组件:
|
||||
sysbench/geekbench (CPU性能测试)
|
||||
sudo, tar, unzip, dd, fio
|
||||
speedtest (网络测试)
|
||||
ping (网络连通性测试)
|
||||
systemd-detect-virt/dmidecode (系统信息检测)
|
||||
|
||||
./goecs.sh install 安装 goecs 命令
|
||||
./goecs.sh upgrade 升级 goecs 命令
|
||||
./goecs.sh uninstall 卸载 goecs 命令
|
||||
./goecs.sh help 显示此消息
|
||||
|
||||
Available commands:
|
||||
|
||||
env Check and Install package: dd fio sysbench geekbench5 speedtest
|
||||
install Install goecs command
|
||||
upgrade Upgrade goecs command
|
||||
help Show this message
|
||||
./goecs.sh env Check and Install dependencies
|
||||
Note: macOS systems will skip dependency installation
|
||||
Warning: This command performs system update(optional), which may:
|
||||
1. Take considerable time
|
||||
2. Cause temporary network interruptions
|
||||
3. Impact system stability
|
||||
4. Affect subsequent system startups
|
||||
For systems with less than 1GB RAM, additional risks:
|
||||
1. System freeze
|
||||
2. SSH connection loss
|
||||
3. Critical service failures
|
||||
Recommended:
|
||||
Hanging execution during environment dependency installation
|
||||
|
||||
Optional components:
|
||||
sysbench/geekbench (CPU testing)
|
||||
sudo, tar, unzip, dd, fio
|
||||
speedtest (Network testing)
|
||||
ping (Network connectivity)
|
||||
systemd-detect-virt/dmidecode (System info detection)
|
||||
|
||||
./goecs.sh install Install goecs command
|
||||
./goecs.sh upgrade Upgrade goecs command
|
||||
./goecs.sh uninstall Uninstall goecs command
|
||||
./goecs.sh help Show this message
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -315,6 +683,9 @@ case "$1" in
|
||||
"install" | "upgrade")
|
||||
goecs_check
|
||||
;;
|
||||
"uninstall")
|
||||
uninstall_goecs
|
||||
;;
|
||||
*)
|
||||
echo "No command found."
|
||||
echo
|
||||
|
||||
343
internal/menu/menu.go
Normal file
343
internal/menu/menu.go
Normal file
@@ -0,0 +1,343 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
// GetMenuChoice prompts user for menu choice
|
||||
func GetMenuChoice(language string) string {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer signal.Stop(sigChan)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-sigChan:
|
||||
if language == "zh" {
|
||||
fmt.Println("\n程序在选择过程中被用户中断")
|
||||
} else {
|
||||
fmt.Println("\nProgram interrupted by user during selection")
|
||||
}
|
||||
os.Exit(0)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
var input string
|
||||
if language == "zh" {
|
||||
fmt.Print("请输入选项: ")
|
||||
} else {
|
||||
fmt.Print("Please enter your choice: ")
|
||||
}
|
||||
fmt.Scanln(&input)
|
||||
input = strings.TrimSpace(input)
|
||||
input = strings.TrimRight(input, "\n")
|
||||
|
||||
re := regexp.MustCompile(`^\d+$`)
|
||||
if re.MatchString(input) {
|
||||
inChoice := input
|
||||
switch inChoice {
|
||||
case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10":
|
||||
return inChoice
|
||||
default:
|
||||
if language == "zh" {
|
||||
fmt.Println("无效的选项")
|
||||
} else {
|
||||
fmt.Println("Invalid choice")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if language == "zh" {
|
||||
fmt.Println("输入错误,请输入一个纯数字")
|
||||
} else {
|
||||
fmt.Println("Invalid input, please enter a number")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintMenuOptions displays menu options
|
||||
func PrintMenuOptions(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
var stats *utils.StatsResponse
|
||||
var statsErr error
|
||||
var githubInfo *utils.GitHubRelease
|
||||
var githubErr error
|
||||
if preCheck.Connected {
|
||||
var pwg sync.WaitGroup
|
||||
pwg.Add(2)
|
||||
go func() {
|
||||
defer pwg.Done()
|
||||
stats, statsErr = utils.GetGoescStats()
|
||||
}()
|
||||
go func() {
|
||||
defer pwg.Done()
|
||||
githubInfo, githubErr = utils.GetLatestEcsRelease()
|
||||
}()
|
||||
pwg.Wait()
|
||||
} else {
|
||||
statsErr = fmt.Errorf("network not connected")
|
||||
githubErr = fmt.Errorf("network not connected")
|
||||
}
|
||||
var statsInfo string
|
||||
var cmp int
|
||||
if preCheck.Connected {
|
||||
if statsErr != nil {
|
||||
statsInfo = "NULL"
|
||||
} else {
|
||||
switch config.Language {
|
||||
case "zh":
|
||||
statsInfo = fmt.Sprintf("总使用量: %s | 今日使用: %s",
|
||||
utils.FormatGoecsNumber(stats.Total),
|
||||
utils.FormatGoecsNumber(stats.Daily))
|
||||
case "en":
|
||||
statsInfo = fmt.Sprintf("Total Usage: %s | Daily Usage: %s",
|
||||
utils.FormatGoecsNumber(stats.Total),
|
||||
utils.FormatGoecsNumber(stats.Daily))
|
||||
}
|
||||
}
|
||||
if githubErr == nil {
|
||||
cmp = utils.CompareVersions(config.EcsVersion, githubInfo.TagName)
|
||||
} else {
|
||||
cmp = 0
|
||||
}
|
||||
}
|
||||
switch config.Language {
|
||||
case "zh":
|
||||
fmt.Printf("VPS融合怪版本: %s\n", config.EcsVersion)
|
||||
if preCheck.Connected {
|
||||
switch cmp {
|
||||
case -1:
|
||||
fmt.Printf("检测到新版本 %s 如有必要请更新!\n", githubInfo.TagName)
|
||||
}
|
||||
fmt.Printf("使用统计: %s\n", statsInfo)
|
||||
}
|
||||
fmt.Println("1. 融合怪完全体(能测全测)")
|
||||
fmt.Println("2. 极简版(系统信息+CPU+内存+磁盘+测速节点5个)")
|
||||
fmt.Println("3. 精简版(系统信息+CPU+内存+磁盘+跨国平台解锁+路由+测速节点5个)")
|
||||
fmt.Println("4. 精简网络版(系统信息+CPU+内存+磁盘+回程+路由+测速节点5个)")
|
||||
fmt.Println("5. 精简解锁版(系统信息+CPU+内存+磁盘IO+跨国平台解锁+测速节点5个)")
|
||||
fmt.Println("6. 网络单项(IP质量检测+上游及三网回程+广州三网回程详细路由+全国延迟+TGDC+网站延迟+测速节点11个)")
|
||||
fmt.Println("7. 解锁单项(跨国平台解锁)")
|
||||
fmt.Println("8. 硬件单项(系统信息+CPU+dd磁盘测试+fio磁盘测试)")
|
||||
fmt.Println("9. IP质量检测(15个数据库的IP质量检测+邮件端口检测)")
|
||||
fmt.Println("10. 三网回程线路检测+三网回程详细路由(北京上海广州成都)+全国延迟+TGDC+网站延迟")
|
||||
fmt.Println("0. 退出程序")
|
||||
case "en":
|
||||
fmt.Printf("VPS Fusion Monster Test Version: %s\n", config.EcsVersion)
|
||||
if preCheck.Connected {
|
||||
switch cmp {
|
||||
case -1:
|
||||
fmt.Printf("New version detected %s update if necessary!\n", githubInfo.TagName)
|
||||
}
|
||||
fmt.Printf("%s\n", statsInfo)
|
||||
}
|
||||
fmt.Println("1. VPS Fusion Monster Test (Full Test)")
|
||||
fmt.Println("2. Minimal Test Suite (System Info + CPU + Memory + Disk + 5 Speed Test Nodes)")
|
||||
fmt.Println("3. Standard Test Suite (System Info + CPU + Memory + Disk + International Platform Unlock + Routing + 5 Speed Test Nodes)")
|
||||
fmt.Println("4. Network-Focused Test Suite (System Info + CPU + Memory + Disk + Backtrace + Routing + 5 Speed Test Nodes)")
|
||||
fmt.Println("5. Unlock-Focused Test Suite (System Info + CPU + Memory + Disk IO + International Platform Unlock + 5 Speed Test Nodes)")
|
||||
fmt.Println("6. Network-Only Test (IP Quality Test + Upstream & 3-Network Backtrace + Guangzhou 3-Network Detailed Routing + National Latency + TGDC + Websites + 11 Speed Test Nodes)")
|
||||
fmt.Println("7. Unlock-Only Test (International Platform Unlock)")
|
||||
fmt.Println("8. Hardware-Only Test (System Info + CPU + Memory + dd Disk Test + fio Disk Test)")
|
||||
fmt.Println("9. IP Quality Test (IP Test with 15 Databases + Email Port Test)")
|
||||
fmt.Println("0. Exit Program")
|
||||
}
|
||||
}
|
||||
|
||||
// HandleMenuMode handles menu selection using the interactive TUI
|
||||
func HandleMenuMode(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
savedParams := config.SaveUserSetParams()
|
||||
config.BasicStatus = false
|
||||
config.CpuTestStatus = false
|
||||
config.MemoryTestStatus = false
|
||||
config.DiskTestStatus = false
|
||||
config.UtTestStatus = false
|
||||
config.SecurityTestStatus = false
|
||||
config.EmailTestStatus = false
|
||||
config.BacktraceStatus = false
|
||||
config.Nt3Status = false
|
||||
config.SpeedTestStatus = false
|
||||
config.TgdcTestStatus = false
|
||||
config.WebTestStatus = false
|
||||
config.AutoChangeDiskMethod = true
|
||||
|
||||
result := RunTuiMenu(preCheck, config)
|
||||
if result.quit {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Update language if changed by TUI selection
|
||||
config.Language = result.language
|
||||
|
||||
if result.custom {
|
||||
applyCustomResult(result, preCheck, config)
|
||||
if config.SpeedTestStatus {
|
||||
config.OnlyChinaTest = utils.CheckChina(config.EnableLogger, config.Language)
|
||||
}
|
||||
} else {
|
||||
config.Choice = result.choice
|
||||
switch result.choice {
|
||||
case "0":
|
||||
os.Exit(0)
|
||||
case "1":
|
||||
SetFullTestStatus(preCheck, config)
|
||||
config.OnlyChinaTest = utils.CheckChina(config.EnableLogger, config.Language)
|
||||
case "2":
|
||||
SetMinimalTestStatus(preCheck, config)
|
||||
case "3":
|
||||
SetStandardTestStatus(preCheck, config)
|
||||
case "4":
|
||||
SetNetworkFocusedTestStatus(preCheck, config)
|
||||
case "5":
|
||||
SetUnlockFocusedTestStatus(preCheck, config)
|
||||
case "6":
|
||||
SetNetworkOnlyTestStatus(config)
|
||||
case "7":
|
||||
SetUnlockOnlyTestStatus(config)
|
||||
case "8":
|
||||
SetHardwareOnlyTestStatus(preCheck, config)
|
||||
case "9":
|
||||
SetIPQualityTestStatus(config)
|
||||
case "10":
|
||||
config.Nt3Location = "ALL"
|
||||
SetRouteTestStatus(config)
|
||||
}
|
||||
}
|
||||
config.RestoreUserSetParams(savedParams)
|
||||
}
|
||||
|
||||
// SetFullTestStatus enables all tests
|
||||
func SetFullTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
config.UtTestStatus = true
|
||||
config.SecurityTestStatus = true
|
||||
config.EmailTestStatus = true
|
||||
config.BacktraceStatus = true
|
||||
config.Nt3Status = true
|
||||
config.SpeedTestStatus = true
|
||||
config.TgdcTestStatus = true
|
||||
config.WebTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetMinimalTestStatus sets minimal test configuration
|
||||
func SetMinimalTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
config.SpeedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetStandardTestStatus sets standard test configuration
|
||||
func SetStandardTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
config.UtTestStatus = true
|
||||
config.Nt3Status = true
|
||||
config.SpeedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetNetworkFocusedTestStatus sets network-focused test configuration
|
||||
func SetNetworkFocusedTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
config.BacktraceStatus = true
|
||||
config.Nt3Status = true
|
||||
config.SpeedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetUnlockFocusedTestStatus sets unlock-focused test configuration
|
||||
func SetUnlockFocusedTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
config.UtTestStatus = true
|
||||
config.SpeedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
// SetNetworkOnlyTestStatus sets network-only test configuration
|
||||
func SetNetworkOnlyTestStatus(config *params.Config) {
|
||||
config.OnlyIpInfoCheck = true
|
||||
config.SecurityTestStatus = true
|
||||
config.SpeedTestStatus = true
|
||||
config.BacktraceStatus = true
|
||||
config.Nt3Status = true
|
||||
config.PingTestStatus = true
|
||||
config.TgdcTestStatus = true
|
||||
config.WebTestStatus = true
|
||||
}
|
||||
|
||||
// SetUnlockOnlyTestStatus sets unlock-only test configuration
|
||||
func SetUnlockOnlyTestStatus(config *params.Config) {
|
||||
config.OnlyIpInfoCheck = true
|
||||
config.UtTestStatus = true
|
||||
}
|
||||
|
||||
// SetHardwareOnlyTestStatus sets hardware-only test configuration
|
||||
func SetHardwareOnlyTestStatus(preCheck utils.NetCheckResult, config *params.Config) {
|
||||
_ = preCheck
|
||||
config.BasicStatus = true
|
||||
config.CpuTestStatus = true
|
||||
config.MemoryTestStatus = true
|
||||
config.DiskTestStatus = true
|
||||
config.SecurityTestStatus = false
|
||||
config.AutoChangeDiskMethod = false
|
||||
}
|
||||
|
||||
// SetIPQualityTestStatus sets IP quality test configuration
|
||||
func SetIPQualityTestStatus(config *params.Config) {
|
||||
config.OnlyIpInfoCheck = true
|
||||
config.SecurityTestStatus = true
|
||||
config.EmailTestStatus = true
|
||||
}
|
||||
|
||||
// SetRouteTestStatus sets route test configuration
|
||||
func SetRouteTestStatus(config *params.Config) {
|
||||
config.OnlyIpInfoCheck = true
|
||||
config.BacktraceStatus = true
|
||||
config.Nt3Status = true
|
||||
config.PingTestStatus = true
|
||||
config.TgdcTestStatus = true
|
||||
config.WebTestStatus = true
|
||||
}
|
||||
|
||||
// PrintInvalidChoice prints invalid choice message
|
||||
func PrintInvalidChoice(language string) {
|
||||
if language == "zh" {
|
||||
fmt.Println("无效的选项")
|
||||
} else {
|
||||
fmt.Println("Invalid choice")
|
||||
}
|
||||
}
|
||||
762
internal/menu/tui.go
Normal file
762
internal/menu/tui.go
Normal file
@@ -0,0 +1,762 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
// --- Styles ---
|
||||
|
||||
var (
|
||||
tTitleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("39"))
|
||||
tInfoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
|
||||
tWarnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("214"))
|
||||
tSelStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Bold(true)
|
||||
tNormStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252"))
|
||||
tDimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
|
||||
tHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
|
||||
tSectStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("214")).Bold(true)
|
||||
tChkOnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("120"))
|
||||
tChkOffStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
|
||||
tBtnStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("0")).Background(lipgloss.Color("120")).Bold(true).Padding(0, 2)
|
||||
tBtnDimStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")).Background(lipgloss.Color("238")).Padding(0, 2)
|
||||
)
|
||||
|
||||
// --- Types ---
|
||||
|
||||
type menuPhase int
|
||||
|
||||
const (
|
||||
phaseLang menuPhase = iota
|
||||
phaseMain
|
||||
phaseCustom
|
||||
)
|
||||
|
||||
type mainMenuItem struct {
|
||||
id string
|
||||
zh string
|
||||
en string
|
||||
needNet bool
|
||||
}
|
||||
|
||||
type testToggle struct {
|
||||
nameZh string
|
||||
nameEn string
|
||||
enabled bool
|
||||
needNet bool
|
||||
}
|
||||
|
||||
type advSetting struct {
|
||||
nameZh string
|
||||
nameEn string
|
||||
options []string
|
||||
current int
|
||||
}
|
||||
|
||||
type tuiResult struct {
|
||||
choice string
|
||||
language string
|
||||
quit bool
|
||||
custom bool
|
||||
toggles []testToggle
|
||||
advanced []advSetting
|
||||
}
|
||||
|
||||
type tuiModel struct {
|
||||
phase menuPhase
|
||||
config *params.Config
|
||||
preCheck utils.NetCheckResult
|
||||
langPreset bool
|
||||
|
||||
// Language selection
|
||||
langCursor int
|
||||
|
||||
// Main menu
|
||||
mainCursor int
|
||||
mainItems []mainMenuItem
|
||||
|
||||
// Custom mode
|
||||
customCursor int
|
||||
toggles []testToggle
|
||||
advanced []advSetting
|
||||
customTotal int // toggles + advanced + 1 (confirm button)
|
||||
|
||||
// Pre-loaded info
|
||||
statsTotal int
|
||||
statsDaily int
|
||||
hasStats bool
|
||||
cmpVersion int
|
||||
newVersion string
|
||||
|
||||
// Result
|
||||
result tuiResult
|
||||
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// --- Default data ---
|
||||
|
||||
func defaultMainItems() []mainMenuItem {
|
||||
return []mainMenuItem{
|
||||
{id: "1", zh: "融合怪完全体(能测全测)", en: "Full Test (All Available Tests)", needNet: false},
|
||||
{id: "2", zh: "极简版(系统信息+CPU+内存+磁盘+测速节点5个)", en: "Minimal Suite (SysInfo+CPU+Mem+Disk+5 Speed Nodes)", needNet: false},
|
||||
{id: "3", zh: "精简版(系统信息+CPU+内存+磁盘+解锁+路由+测速节点5个)", en: "Standard Suite (SysInfo+CPU+Mem+Disk+Unlock+Route+5 Speed Nodes)", needNet: false},
|
||||
{id: "4", zh: "精简网络版(系统信息+CPU+内存+磁盘+回程+路由+测速节点5个)", en: "Network Suite (SysInfo+CPU+Mem+Disk+Backtrace+Route+5 Speed Nodes)", needNet: false},
|
||||
{id: "5", zh: "精简解锁版(系统信息+CPU+内存+磁盘IO+解锁+测速节点5个)", en: "Unlock Suite (SysInfo+CPU+Mem+DiskIO+Unlock+5 Speed Nodes)", needNet: false},
|
||||
{id: "6", zh: "网络单项(IP质量+回程+路由+延迟+TGDC+网站+测速节点11个)", en: "Network Only (IPQuality+Backtrace+Route+Latency+TGDC+Web+11 Speed Nodes)", needNet: true},
|
||||
{id: "7", zh: "解锁单项(跨国平台解锁)", en: "Unlock Only (International Platform Unlock)", needNet: true},
|
||||
{id: "8", zh: "硬件单项(系统信息+CPU+内存+dd磁盘+fio磁盘)", en: "Hardware Only (SysInfo+CPU+Mem+DD Disk+FIO Disk)", needNet: false},
|
||||
{id: "9", zh: "IP质量检测(15个数据库+邮件端口检测)", en: "IP Quality (15 Databases + Email Port Test)", needNet: true},
|
||||
{id: "10", zh: "三网回程线路+路由+延迟+TGDC+网站延迟", en: "3-Net Backtrace+Route+Latency+TGDC+Websites", needNet: true},
|
||||
{id: "custom", zh: ">>> 自定义测试(自由选择测试项和高级设置)", en: ">>> Custom Test (Choose Tests & Advanced Settings)", needNet: false},
|
||||
{id: "0", zh: "退出程序", en: "Exit Program", needNet: false},
|
||||
}
|
||||
}
|
||||
|
||||
func defaultTestToggles() []testToggle {
|
||||
return []testToggle{
|
||||
{nameZh: "基础系统信息", nameEn: "Basic System Info", enabled: true, needNet: false},
|
||||
{nameZh: "CPU测试", nameEn: "CPU Test", enabled: true, needNet: false},
|
||||
{nameZh: "内存测试", nameEn: "Memory Test", enabled: true, needNet: false},
|
||||
{nameZh: "磁盘测试", nameEn: "Disk Test", enabled: true, needNet: false},
|
||||
{nameZh: "跨国平台解锁", nameEn: "Streaming Unlock", enabled: false, needNet: true},
|
||||
{nameZh: "IP质量检测", nameEn: "IP Quality Check", enabled: false, needNet: true},
|
||||
{nameZh: "邮件端口检测", nameEn: "Email Port Check", enabled: false, needNet: true},
|
||||
{nameZh: "回程路由", nameEn: "Backtrace Route", enabled: false, needNet: true},
|
||||
{nameZh: "NT3路由", nameEn: "NT3 Route", enabled: false, needNet: true},
|
||||
{nameZh: "测速", nameEn: "Speed Test", enabled: false, needNet: true},
|
||||
{nameZh: "Ping测试", nameEn: "Ping Test", enabled: false, needNet: true},
|
||||
{nameZh: "TGDC测试", nameEn: "TGDC Test", enabled: false, needNet: true},
|
||||
{nameZh: "网站延迟", nameEn: "Website Latency", enabled: false, needNet: true},
|
||||
}
|
||||
}
|
||||
|
||||
func defaultAdvSettings(config *params.Config) []advSetting {
|
||||
adv := []advSetting{
|
||||
{nameZh: "CPU测试方法", nameEn: "CPU Method", options: []string{"sysbench", "geekbench", "winsat"}, current: 0},
|
||||
{nameZh: "CPU线程模式", nameEn: "CPU Thread", options: []string{"multi", "single"}, current: 0},
|
||||
{nameZh: "内存测试方法", nameEn: "Memory Method", options: []string{"stream", "sysbench", "dd", "winsat", "auto"}, current: 0},
|
||||
{nameZh: "磁盘测试方法", nameEn: "Disk Method", options: []string{"fio", "dd", "winsat"}, current: 0},
|
||||
{nameZh: "NT3测试位置", nameEn: "NT3 Location", options: []string{"GZ", "SH", "BJ", "CD", "ALL"}, current: 0},
|
||||
{nameZh: "NT3测试类型", nameEn: "NT3 Type", options: []string{"ipv4", "ipv6", "both"}, current: 0},
|
||||
{nameZh: "测速节点数/运营商", nameEn: "Speed Nodes/ISP", options: []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, current: 1},
|
||||
}
|
||||
// Set current values from config
|
||||
setAdvCurrent := func(idx int, val string) {
|
||||
for j, opt := range adv[idx].options {
|
||||
if opt == val {
|
||||
adv[idx].current = j
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
setAdvCurrent(0, config.CpuTestMethod)
|
||||
setAdvCurrent(1, config.CpuTestThreadMode)
|
||||
setAdvCurrent(2, config.MemoryTestMethod)
|
||||
setAdvCurrent(3, config.DiskTestMethod)
|
||||
setAdvCurrent(4, config.Nt3Location)
|
||||
setAdvCurrent(5, config.Nt3CheckType)
|
||||
if config.SpNum >= 1 && config.SpNum <= 10 {
|
||||
adv[6].current = config.SpNum - 1
|
||||
}
|
||||
return adv
|
||||
}
|
||||
|
||||
func newTuiModel(preCheck utils.NetCheckResult, config *params.Config, langPreset bool, statsTotal, statsDaily int, hasStats bool, cmpVersion int, newVersion string) tuiModel {
|
||||
toggles := defaultTestToggles()
|
||||
advanced := defaultAdvSettings(config)
|
||||
m := tuiModel{
|
||||
config: config,
|
||||
preCheck: preCheck,
|
||||
langPreset: langPreset,
|
||||
mainItems: defaultMainItems(),
|
||||
toggles: toggles,
|
||||
advanced: advanced,
|
||||
customTotal: len(toggles) + len(advanced) + 1,
|
||||
statsTotal: statsTotal,
|
||||
statsDaily: statsDaily,
|
||||
hasStats: hasStats,
|
||||
cmpVersion: cmpVersion,
|
||||
newVersion: newVersion,
|
||||
width: 80,
|
||||
height: 24,
|
||||
}
|
||||
if langPreset {
|
||||
m.phase = phaseMain
|
||||
m.result.language = config.Language
|
||||
} else {
|
||||
m.phase = phaseLang
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// --- Bubbletea Interface ---
|
||||
|
||||
func (m tuiModel) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
switch m.phase {
|
||||
case phaseLang:
|
||||
return m.updateLang(msg)
|
||||
case phaseMain:
|
||||
return m.updateMain(msg)
|
||||
case phaseCustom:
|
||||
return m.updateCustom(msg)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m tuiModel) View() string {
|
||||
switch m.phase {
|
||||
case phaseLang:
|
||||
return m.viewLang()
|
||||
case phaseMain:
|
||||
return m.viewMain()
|
||||
case phaseCustom:
|
||||
return m.viewCustom()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// --- Language Selection ---
|
||||
|
||||
func (m tuiModel) updateLang(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
switch msg.String() {
|
||||
case "up", "k":
|
||||
if m.langCursor > 0 {
|
||||
m.langCursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if m.langCursor < 1 {
|
||||
m.langCursor++
|
||||
}
|
||||
case "1":
|
||||
m.result.language = "zh"
|
||||
m.phase = phaseMain
|
||||
case "2":
|
||||
m.result.language = "en"
|
||||
m.phase = phaseMain
|
||||
case "enter":
|
||||
if m.langCursor == 0 {
|
||||
m.result.language = "zh"
|
||||
} else {
|
||||
m.result.language = "en"
|
||||
}
|
||||
m.phase = phaseMain
|
||||
case "q", "ctrl+c":
|
||||
m.result.quit = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m tuiModel) viewLang() string {
|
||||
var s strings.Builder
|
||||
s.WriteString("\n")
|
||||
s.WriteString(tTitleStyle.Render(" VPS融合怪测试 / VPS Fusion Monster Test"))
|
||||
s.WriteString("\n\n")
|
||||
s.WriteString(tInfoStyle.Render(" 请选择语言 / Please select language:"))
|
||||
s.WriteString("\n\n")
|
||||
|
||||
langs := []struct {
|
||||
label string
|
||||
desc string
|
||||
}{
|
||||
{"1. 中文", "Chinese"},
|
||||
{"2. English", "英文"},
|
||||
}
|
||||
|
||||
for i, l := range langs {
|
||||
cursor := " "
|
||||
style := tNormStyle
|
||||
if m.langCursor == i {
|
||||
cursor = " > "
|
||||
style = tSelStyle
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%s%s %s\n", cursor, style.Render(l.label), tDimStyle.Render(l.desc)))
|
||||
}
|
||||
|
||||
s.WriteString("\n")
|
||||
s.WriteString(tHelpStyle.Render(" ↑/↓ Navigate Enter Confirm 1/2 Quick-Select q Quit"))
|
||||
s.WriteString("\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// --- Main Menu ---
|
||||
|
||||
func (m tuiModel) updateMain(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
key := msg.String()
|
||||
switch key {
|
||||
case "up", "k":
|
||||
if m.mainCursor > 0 {
|
||||
m.mainCursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if m.mainCursor < len(m.mainItems)-1 {
|
||||
m.mainCursor++
|
||||
}
|
||||
case "home":
|
||||
m.mainCursor = 0
|
||||
case "end":
|
||||
m.mainCursor = len(m.mainItems) - 1
|
||||
case "enter":
|
||||
item := m.mainItems[m.mainCursor]
|
||||
if item.needNet && !m.preCheck.Connected {
|
||||
return m, nil // can't select network-dependent items without connection
|
||||
}
|
||||
if item.id == "custom" {
|
||||
m.phase = phaseCustom
|
||||
m.customCursor = 0
|
||||
return m, nil
|
||||
}
|
||||
m.result.choice = item.id
|
||||
return m, tea.Quit
|
||||
case "q", "ctrl+c":
|
||||
m.result.quit = true
|
||||
return m, tea.Quit
|
||||
default:
|
||||
// Number quick-select
|
||||
for i, item := range m.mainItems {
|
||||
if key == item.id {
|
||||
if item.needNet && !m.preCheck.Connected {
|
||||
return m, nil
|
||||
}
|
||||
if item.id == "custom" {
|
||||
m.phase = phaseCustom
|
||||
m.customCursor = 0
|
||||
return m, nil
|
||||
}
|
||||
m.mainCursor = i
|
||||
m.result.choice = item.id
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m tuiModel) viewMain() string {
|
||||
lang := m.result.language
|
||||
var s strings.Builder
|
||||
s.WriteString("\n")
|
||||
|
||||
// Title
|
||||
if lang == "zh" {
|
||||
s.WriteString(tTitleStyle.Render(fmt.Sprintf(" VPS融合怪 %s", m.config.EcsVersion)))
|
||||
} else {
|
||||
s.WriteString(tTitleStyle.Render(fmt.Sprintf(" VPS Fusion Monster %s", m.config.EcsVersion)))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
|
||||
// Version warning
|
||||
if m.preCheck.Connected && m.cmpVersion == -1 {
|
||||
if lang == "zh" {
|
||||
s.WriteString(tWarnStyle.Render(fmt.Sprintf(" ! 检测到新版本 %s 如有必要请更新!", m.newVersion)))
|
||||
} else {
|
||||
s.WriteString(tWarnStyle.Render(fmt.Sprintf(" ! New version %s detected, update if necessary!", m.newVersion)))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
|
||||
// Stats
|
||||
if m.preCheck.Connected && m.hasStats {
|
||||
if lang == "zh" {
|
||||
s.WriteString(tInfoStyle.Render(fmt.Sprintf(" 总使用量: %s | 今日使用: %s",
|
||||
utils.FormatGoecsNumber(m.statsTotal), utils.FormatGoecsNumber(m.statsDaily))))
|
||||
} else {
|
||||
s.WriteString(tInfoStyle.Render(fmt.Sprintf(" Total Usage: %s | Daily Usage: %s",
|
||||
utils.FormatGoecsNumber(m.statsTotal), utils.FormatGoecsNumber(m.statsDaily))))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
s.WriteString("\n")
|
||||
|
||||
// Section header
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 请选择测试方案:"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Select Test Suite:"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
|
||||
// Menu items
|
||||
for i, item := range m.mainItems {
|
||||
cursor := " "
|
||||
style := tNormStyle
|
||||
if m.mainCursor == i {
|
||||
cursor = " > "
|
||||
style = tSelStyle
|
||||
}
|
||||
|
||||
var label string
|
||||
if lang == "zh" {
|
||||
label = item.zh
|
||||
} else {
|
||||
label = item.en
|
||||
}
|
||||
|
||||
// Number prefix
|
||||
prefix := ""
|
||||
switch {
|
||||
case item.id == "custom":
|
||||
prefix = ""
|
||||
case item.id == "0":
|
||||
prefix = " 0. "
|
||||
default:
|
||||
prefix = fmt.Sprintf("%2s. ", item.id)
|
||||
}
|
||||
|
||||
// Disabled indicator for network-dependent items when no connection
|
||||
suffix := ""
|
||||
if item.needNet && !m.preCheck.Connected {
|
||||
style = tDimStyle
|
||||
if lang == "zh" {
|
||||
suffix = " [需要网络]"
|
||||
} else {
|
||||
suffix = " [No Network]"
|
||||
}
|
||||
}
|
||||
|
||||
s.WriteString(fmt.Sprintf("%s%s%s\n", cursor, style.Render(prefix+label), tDimStyle.Render(suffix)))
|
||||
}
|
||||
|
||||
s.WriteString("\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tHelpStyle.Render(" ↑/↓/j/k 移动 Enter 确认 数字 快速选择 q 退出"))
|
||||
} else {
|
||||
s.WriteString(tHelpStyle.Render(" Up/Down/j/k Navigate Enter Confirm Number Quick-Select q Quit"))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// --- Custom Mode ---
|
||||
|
||||
func (m tuiModel) updateCustom(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
key := msg.String()
|
||||
switch key {
|
||||
case "up", "k":
|
||||
if m.customCursor > 0 {
|
||||
m.customCursor--
|
||||
}
|
||||
case "down", "j":
|
||||
if m.customCursor < m.customTotal-1 {
|
||||
m.customCursor++
|
||||
}
|
||||
case "home":
|
||||
m.customCursor = 0
|
||||
case "end":
|
||||
m.customCursor = m.customTotal - 1
|
||||
case " ":
|
||||
if m.customCursor < len(m.toggles) {
|
||||
// Toggle test item
|
||||
t := &m.toggles[m.customCursor]
|
||||
if t.needNet && !m.preCheck.Connected {
|
||||
break // can't enable without network
|
||||
}
|
||||
t.enabled = !t.enabled
|
||||
} else if m.customCursor < len(m.toggles)+len(m.advanced) {
|
||||
// Cycle advanced setting forward
|
||||
idx := m.customCursor - len(m.toggles)
|
||||
a := &m.advanced[idx]
|
||||
a.current = (a.current + 1) % len(a.options)
|
||||
}
|
||||
case "right", "l":
|
||||
if m.customCursor >= len(m.toggles) && m.customCursor < len(m.toggles)+len(m.advanced) {
|
||||
idx := m.customCursor - len(m.toggles)
|
||||
a := &m.advanced[idx]
|
||||
a.current = (a.current + 1) % len(a.options)
|
||||
}
|
||||
case "left", "h":
|
||||
if m.customCursor >= len(m.toggles) && m.customCursor < len(m.toggles)+len(m.advanced) {
|
||||
idx := m.customCursor - len(m.toggles)
|
||||
a := &m.advanced[idx]
|
||||
a.current = (a.current - 1 + len(a.options)) % len(a.options)
|
||||
}
|
||||
case "a":
|
||||
// Toggle all test items
|
||||
allEnabled := true
|
||||
for _, t := range m.toggles {
|
||||
if !t.enabled && (!t.needNet || m.preCheck.Connected) {
|
||||
allEnabled = false
|
||||
break
|
||||
}
|
||||
}
|
||||
for i := range m.toggles {
|
||||
if m.toggles[i].needNet && !m.preCheck.Connected {
|
||||
continue
|
||||
}
|
||||
m.toggles[i].enabled = !allEnabled
|
||||
}
|
||||
case "enter":
|
||||
if m.customCursor == m.customTotal-1 {
|
||||
// Confirm button
|
||||
m.result.custom = true
|
||||
m.result.choice = "custom"
|
||||
m.result.toggles = m.toggles
|
||||
m.result.advanced = m.advanced
|
||||
return m, tea.Quit
|
||||
}
|
||||
// Toggle current item (same as space)
|
||||
if m.customCursor < len(m.toggles) {
|
||||
t := &m.toggles[m.customCursor]
|
||||
if t.needNet && !m.preCheck.Connected {
|
||||
break
|
||||
}
|
||||
t.enabled = !t.enabled
|
||||
} else if m.customCursor < len(m.toggles)+len(m.advanced) {
|
||||
idx := m.customCursor - len(m.toggles)
|
||||
a := &m.advanced[idx]
|
||||
a.current = (a.current + 1) % len(a.options)
|
||||
}
|
||||
case "esc":
|
||||
m.phase = phaseMain
|
||||
return m, nil
|
||||
case "q", "ctrl+c":
|
||||
m.result.quit = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m tuiModel) viewCustom() string {
|
||||
lang := m.result.language
|
||||
var s strings.Builder
|
||||
s.WriteString("\n")
|
||||
|
||||
if lang == "zh" {
|
||||
s.WriteString(tTitleStyle.Render(" 自定义测试配置"))
|
||||
} else {
|
||||
s.WriteString(tTitleStyle.Render(" Custom Test Configuration"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
|
||||
// Test toggles
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 测试项目 (空格切换, a 全选/全不选):"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Test Items (Space toggle, a Select all/none):"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
|
||||
for i, t := range m.toggles {
|
||||
cursor := " "
|
||||
if m.customCursor == i {
|
||||
cursor = " > "
|
||||
}
|
||||
|
||||
var check string
|
||||
if t.enabled {
|
||||
check = tChkOnStyle.Render("[x]")
|
||||
} else {
|
||||
check = tChkOffStyle.Render("[ ]")
|
||||
}
|
||||
|
||||
var name string
|
||||
if lang == "zh" {
|
||||
name = t.nameZh
|
||||
} else {
|
||||
name = t.nameEn
|
||||
}
|
||||
|
||||
style := tNormStyle
|
||||
if m.customCursor == i {
|
||||
style = tSelStyle
|
||||
}
|
||||
if t.needNet && !m.preCheck.Connected {
|
||||
style = tDimStyle
|
||||
}
|
||||
|
||||
s.WriteString(fmt.Sprintf("%s%s %s\n", cursor, check, style.Render(name)))
|
||||
}
|
||||
|
||||
// Advanced settings
|
||||
s.WriteString("\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 高级设置 (空格/←/→ 切换选项):"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Advanced Settings (Space/Left/Right cycle):"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
|
||||
for i, a := range m.advanced {
|
||||
idx := len(m.toggles) + i
|
||||
cursor := " "
|
||||
if m.customCursor == idx {
|
||||
cursor = " > "
|
||||
}
|
||||
|
||||
var name string
|
||||
if lang == "zh" {
|
||||
name = a.nameZh
|
||||
} else {
|
||||
name = a.nameEn
|
||||
}
|
||||
|
||||
style := tNormStyle
|
||||
if m.customCursor == idx {
|
||||
style = tSelStyle
|
||||
}
|
||||
|
||||
// Render current value with arrows
|
||||
val := a.options[a.current]
|
||||
var optStr string
|
||||
if m.customCursor == idx {
|
||||
optStr = tSelStyle.Render(fmt.Sprintf("< %s >", val))
|
||||
} else {
|
||||
optStr = tDimStyle.Render(fmt.Sprintf("< %s >", val))
|
||||
}
|
||||
|
||||
s.WriteString(fmt.Sprintf("%s%-22s %s\n", cursor, style.Render(name+":"), optStr))
|
||||
}
|
||||
|
||||
// Confirm button
|
||||
s.WriteString("\n")
|
||||
confirmIdx := m.customTotal - 1
|
||||
if m.customCursor == confirmIdx {
|
||||
if lang == "zh" {
|
||||
s.WriteString(fmt.Sprintf(" %s\n", tBtnStyle.Render(">> 开始测试 <<")))
|
||||
} else {
|
||||
s.WriteString(fmt.Sprintf(" %s\n", tBtnStyle.Render(">> Start Test <<")))
|
||||
}
|
||||
} else {
|
||||
if lang == "zh" {
|
||||
s.WriteString(fmt.Sprintf(" %s\n", tBtnDimStyle.Render(">> 开始测试 <<")))
|
||||
} else {
|
||||
s.WriteString(fmt.Sprintf(" %s\n", tBtnDimStyle.Render(">> Start Test <<")))
|
||||
}
|
||||
}
|
||||
|
||||
s.WriteString("\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tHelpStyle.Render(" ↑/↓ 移动 空格/Enter 切换 ←/→ 调整 a 全选 Esc 返回 q 退出"))
|
||||
} else {
|
||||
s.WriteString(tHelpStyle.Render(" Up/Down Move Space/Enter Toggle Left/Right Adjust a All Esc Back q Quit"))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// --- Public Interface ---
|
||||
|
||||
// RunTuiMenu runs the interactive TUI menu and returns the result
|
||||
func RunTuiMenu(preCheck utils.NetCheckResult, config *params.Config) tuiResult {
|
||||
// Pre-load stats
|
||||
var statsTotal, statsDaily int
|
||||
var hasStats bool
|
||||
var cmpVersion int
|
||||
var newVersion string
|
||||
|
||||
if preCheck.Connected {
|
||||
var wg sync.WaitGroup
|
||||
var stats *utils.StatsResponse
|
||||
var statsErr error
|
||||
var githubInfo *utils.GitHubRelease
|
||||
var githubErr error
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stats, statsErr = utils.GetGoescStats()
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
githubInfo, githubErr = utils.GetLatestEcsRelease()
|
||||
}()
|
||||
wg.Wait()
|
||||
if statsErr == nil {
|
||||
statsTotal = stats.Total
|
||||
statsDaily = stats.Daily
|
||||
hasStats = true
|
||||
}
|
||||
if githubErr == nil {
|
||||
cmpVersion = utils.CompareVersions(config.EcsVersion, githubInfo.TagName)
|
||||
newVersion = githubInfo.TagName
|
||||
}
|
||||
}
|
||||
|
||||
langPreset := config.UserSetFlags["lang"] || config.UserSetFlags["l"]
|
||||
m := newTuiModel(preCheck, config, langPreset, statsTotal, statsDaily, hasStats, cmpVersion, newVersion)
|
||||
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
finalModel, err := p.Run()
|
||||
if err != nil {
|
||||
fmt.Printf("Error running menu: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return finalModel.(tuiModel).result
|
||||
}
|
||||
|
||||
// applyCustomResult applies custom mode selections to config
|
||||
func applyCustomResult(result tuiResult, preCheck utils.NetCheckResult, config *params.Config) {
|
||||
// Apply test toggles (order must match defaultTestToggles)
|
||||
for i, t := range result.toggles {
|
||||
enabled := t.enabled
|
||||
if t.needNet && !preCheck.Connected {
|
||||
enabled = false
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
config.BasicStatus = enabled
|
||||
case 1:
|
||||
config.CpuTestStatus = enabled
|
||||
case 2:
|
||||
config.MemoryTestStatus = enabled
|
||||
case 3:
|
||||
config.DiskTestStatus = enabled
|
||||
case 4:
|
||||
config.UtTestStatus = enabled
|
||||
case 5:
|
||||
config.SecurityTestStatus = enabled
|
||||
case 6:
|
||||
config.EmailTestStatus = enabled
|
||||
case 7:
|
||||
config.BacktraceStatus = enabled
|
||||
case 8:
|
||||
config.Nt3Status = enabled
|
||||
case 9:
|
||||
config.SpeedTestStatus = enabled
|
||||
case 10:
|
||||
config.PingTestStatus = enabled
|
||||
case 11:
|
||||
config.TgdcTestStatus = enabled
|
||||
case 12:
|
||||
config.WebTestStatus = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Apply advanced settings (order must match defaultAdvSettings)
|
||||
if len(result.advanced) >= 7 {
|
||||
config.CpuTestMethod = result.advanced[0].options[result.advanced[0].current]
|
||||
config.CpuTestThreadMode = result.advanced[1].options[result.advanced[1].current]
|
||||
config.MemoryTestMethod = result.advanced[2].options[result.advanced[2].current]
|
||||
config.DiskTestMethod = result.advanced[3].options[result.advanced[3].current]
|
||||
config.Nt3Location = result.advanced[4].options[result.advanced[4].current]
|
||||
config.Nt3CheckType = result.advanced[5].options[result.advanced[5].current]
|
||||
spIdx := result.advanced[6].current
|
||||
config.SpNum = spIdx + 1
|
||||
}
|
||||
|
||||
// Set OnlyIpInfoCheck if no hardware tests are enabled
|
||||
if !config.BasicStatus && !config.CpuTestStatus && !config.MemoryTestStatus && !config.DiskTestStatus {
|
||||
config.OnlyIpInfoCheck = true
|
||||
}
|
||||
|
||||
config.AutoChangeDiskMethod = true
|
||||
}
|
||||
472
internal/params/params.go
Normal file
472
internal/params/params.go
Normal file
@@ -0,0 +1,472 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config holds all configuration parameters
|
||||
type Config struct {
|
||||
EcsVersion string
|
||||
MenuMode bool
|
||||
OnlyChinaTest bool
|
||||
Input string
|
||||
Choice string
|
||||
ShowVersion bool
|
||||
EnableLogger bool
|
||||
Language string
|
||||
CpuTestMethod string
|
||||
CpuTestThreadMode string
|
||||
MemoryTestMethod string
|
||||
DiskTestMethod string
|
||||
DiskTestPath string
|
||||
DiskMultiCheck bool
|
||||
Nt3CheckType string
|
||||
Nt3Location string
|
||||
SpNum int
|
||||
Width int
|
||||
BasicStatus bool
|
||||
CpuTestStatus bool
|
||||
MemoryTestStatus bool
|
||||
DiskTestStatus bool
|
||||
UtTestStatus bool
|
||||
SecurityTestStatus bool
|
||||
EmailTestStatus bool
|
||||
BacktraceStatus bool
|
||||
Nt3Status bool
|
||||
SpeedTestStatus bool
|
||||
PingTestStatus bool
|
||||
TgdcTestStatus bool
|
||||
WebTestStatus bool
|
||||
AutoChangeDiskMethod bool
|
||||
FilePath string
|
||||
EnableUpload bool
|
||||
OnlyIpInfoCheck bool
|
||||
Help bool
|
||||
Finish bool
|
||||
UserSetFlags map[string]bool
|
||||
GoecsFlag *flag.FlagSet
|
||||
}
|
||||
|
||||
// NewConfig creates a new Config with default values
|
||||
func NewConfig(version string) *Config {
|
||||
return &Config{
|
||||
EcsVersion: version,
|
||||
MenuMode: true,
|
||||
OnlyChinaTest: false,
|
||||
Input: "",
|
||||
Choice: "",
|
||||
ShowVersion: false,
|
||||
EnableLogger: false,
|
||||
Language: "zh",
|
||||
CpuTestMethod: "sysbench",
|
||||
CpuTestThreadMode: "multi",
|
||||
MemoryTestMethod: "stream",
|
||||
DiskTestMethod: "fio",
|
||||
DiskTestPath: "",
|
||||
DiskMultiCheck: false,
|
||||
Nt3CheckType: "ipv4",
|
||||
SpNum: 2,
|
||||
Width: 82,
|
||||
BasicStatus: true,
|
||||
CpuTestStatus: true,
|
||||
MemoryTestStatus: true,
|
||||
DiskTestStatus: true,
|
||||
UtTestStatus: true,
|
||||
SecurityTestStatus: true,
|
||||
EmailTestStatus: true,
|
||||
BacktraceStatus: true,
|
||||
Nt3Status: true,
|
||||
SpeedTestStatus: true,
|
||||
PingTestStatus: false,
|
||||
TgdcTestStatus: false,
|
||||
WebTestStatus: false,
|
||||
AutoChangeDiskMethod: true,
|
||||
FilePath: "goecs.txt",
|
||||
EnableUpload: true,
|
||||
OnlyIpInfoCheck: false,
|
||||
Help: false,
|
||||
Finish: false,
|
||||
UserSetFlags: make(map[string]bool),
|
||||
GoecsFlag: flag.NewFlagSet("goecs", flag.ContinueOnError),
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeBoolArgs preprocesses args so that bool flags written as
|
||||
// "-flag true" or "-flag false" (space-separated) are converted to
|
||||
// "-flag=true" / "-flag=false" that the standard flag package understands.
|
||||
// This also strips any duplicate spaces that may appear between tokens when
|
||||
// args have been assembled by shell scripts or other callers.
|
||||
func normalizeBoolArgs(args []string) []string {
|
||||
// All known boolean flag names (without leading dash).
|
||||
boolFlags := map[string]bool{
|
||||
"h": true, "help": true, "v": true, "version": true,
|
||||
"menu": true, "basic": true, "cpu": true, "memory": true,
|
||||
"disk": true, "ut": true, "security": true, "email": true,
|
||||
"backtrace": true, "nt3": true, "speed": true, "ping": true,
|
||||
"tgdc": true, "web": true, "log": true, "upload": true,
|
||||
"diskmc": true,
|
||||
}
|
||||
|
||||
out := make([]string, 0, len(args))
|
||||
i := 0
|
||||
for i < len(args) {
|
||||
arg := args[i]
|
||||
// Skip empty tokens that can appear from split on multiple spaces.
|
||||
if arg == "" {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// Detect flag tokens: -flag or --flag (without embedded =).
|
||||
if strings.HasPrefix(arg, "-") && !strings.Contains(arg, "=") {
|
||||
name := strings.TrimLeft(arg, "-")
|
||||
if boolFlags[name] {
|
||||
// Peek at next token: if it is "true" or "false", merge.
|
||||
if i+1 < len(args) {
|
||||
next := strings.ToLower(strings.TrimSpace(args[i+1]))
|
||||
if next == "true" || next == "false" {
|
||||
out = append(out, arg+"="+next)
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, arg)
|
||||
i++
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ParseFlags parses command line flags
|
||||
func (c *Config) ParseFlags(args []string) {
|
||||
args = normalizeBoolArgs(args)
|
||||
c.GoecsFlag.BoolVar(&c.Help, "h", false, "Show help information")
|
||||
c.GoecsFlag.BoolVar(&c.Help, "help", false, "Show help information")
|
||||
c.GoecsFlag.BoolVar(&c.ShowVersion, "v", false, "Display version information")
|
||||
c.GoecsFlag.BoolVar(&c.ShowVersion, "version", false, "Display version information")
|
||||
c.GoecsFlag.BoolVar(&c.MenuMode, "menu", true, "Enable/Disable menu mode, disable example: -menu=false")
|
||||
c.GoecsFlag.StringVar(&c.Language, "lang", "zh", "Set language (supported: en, zh)")
|
||||
c.GoecsFlag.StringVar(&c.Language, "l", "zh", "Set language (supported: en, zh)")
|
||||
c.GoecsFlag.BoolVar(&c.BasicStatus, "basic", true, "Enable/Disable basic test")
|
||||
c.GoecsFlag.BoolVar(&c.CpuTestStatus, "cpu", true, "Enable/Disable CPU test")
|
||||
c.GoecsFlag.BoolVar(&c.MemoryTestStatus, "memory", true, "Enable/Disable memory test")
|
||||
c.GoecsFlag.BoolVar(&c.DiskTestStatus, "disk", true, "Enable/Disable disk test")
|
||||
c.GoecsFlag.BoolVar(&c.UtTestStatus, "ut", true, "Enable/Disable unlock media test")
|
||||
c.GoecsFlag.BoolVar(&c.SecurityTestStatus, "security", true, "Enable/Disable security test")
|
||||
c.GoecsFlag.BoolVar(&c.EmailTestStatus, "email", true, "Enable/Disable email port test")
|
||||
c.GoecsFlag.BoolVar(&c.BacktraceStatus, "backtrace", true, "Enable/Disable backtrace test (in 'en' language or on windows it always false)")
|
||||
c.GoecsFlag.BoolVar(&c.Nt3Status, "nt3", true, "Enable/Disable NT3 test (in 'en' language or on windows it always false)")
|
||||
c.GoecsFlag.BoolVar(&c.SpeedTestStatus, "speed", true, "Enable/Disable speed test")
|
||||
c.GoecsFlag.BoolVar(&c.PingTestStatus, "ping", false, "Enable/Disable ping test")
|
||||
c.GoecsFlag.BoolVar(&c.TgdcTestStatus, "tgdc", false, "Enable/Disable Telegram DC test")
|
||||
c.GoecsFlag.BoolVar(&c.WebTestStatus, "web", false, "Enable/Disable popular websites test")
|
||||
c.GoecsFlag.StringVar(&c.CpuTestMethod, "cpum", "sysbench", "Set CPU test method (supported: sysbench, geekbench, winsat)")
|
||||
c.GoecsFlag.StringVar(&c.CpuTestMethod, "cpu-method", "sysbench", "Set CPU test method (supported: sysbench, geekbench, winsat)")
|
||||
c.GoecsFlag.StringVar(&c.CpuTestThreadMode, "cput", "multi", "Set CPU test thread mode (supported: single, multi)")
|
||||
c.GoecsFlag.StringVar(&c.CpuTestThreadMode, "cpu-thread", "multi", "Set CPU test thread mode (supported: single, multi)")
|
||||
c.GoecsFlag.StringVar(&c.MemoryTestMethod, "memorym", "stream", "Set memory test method (supported: stream, sysbench, dd, winsat, auto)")
|
||||
c.GoecsFlag.StringVar(&c.MemoryTestMethod, "memory-method", "stream", "Set memory test method (supported: stream, sysbench, dd, winsat, auto)")
|
||||
c.GoecsFlag.StringVar(&c.DiskTestMethod, "diskm", "fio", "Set disk test method (supported: fio, dd, winsat)")
|
||||
c.GoecsFlag.StringVar(&c.DiskTestMethod, "disk-method", "fio", "Set disk test method (supported: fio, dd, winsat)")
|
||||
c.GoecsFlag.StringVar(&c.DiskTestPath, "diskp", "", "Set disk test path, e.g., -diskp /root")
|
||||
c.GoecsFlag.BoolVar(&c.DiskMultiCheck, "diskmc", false, "Enable/Disable multiple disk checks, e.g., -diskmc=false")
|
||||
c.GoecsFlag.StringVar(&c.Nt3Location, "nt3loc", "GZ", "Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all)")
|
||||
c.GoecsFlag.StringVar(&c.Nt3Location, "nt3-location", "GZ", "Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all)")
|
||||
c.GoecsFlag.StringVar(&c.Nt3CheckType, "nt3t", "ipv4", "Set NT3 test type (supported: both, ipv4, ipv6)")
|
||||
c.GoecsFlag.StringVar(&c.Nt3CheckType, "nt3-type", "ipv4", "Set NT3 test type (supported: both, ipv4, ipv6)")
|
||||
c.GoecsFlag.IntVar(&c.SpNum, "spnum", 2, "Set the number of servers per operator for speed test")
|
||||
c.GoecsFlag.BoolVar(&c.EnableLogger, "log", false, "Enable/Disable logging in the current path")
|
||||
c.GoecsFlag.BoolVar(&c.EnableUpload, "upload", true, "Enable/Disable upload the result")
|
||||
c.GoecsFlag.Parse(args)
|
||||
|
||||
c.GoecsFlag.Visit(func(f *flag.Flag) {
|
||||
c.UserSetFlags[f.Name] = true
|
||||
})
|
||||
}
|
||||
|
||||
// HandleHelpAndVersion handles help and version flags
|
||||
func (c *Config) HandleHelpAndVersion(programName string) bool {
|
||||
if c.Help {
|
||||
fmt.Printf("Usage: %s [options]\n", programName)
|
||||
c.GoecsFlag.PrintDefaults()
|
||||
return true
|
||||
}
|
||||
if c.ShowVersion {
|
||||
fmt.Println(c.EcsVersion)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SaveUserSetParams saves user-set parameters
|
||||
func (c *Config) SaveUserSetParams() map[string]interface{} {
|
||||
saved := make(map[string]interface{})
|
||||
|
||||
if c.UserSetFlags["basic"] {
|
||||
saved["basic"] = c.BasicStatus
|
||||
}
|
||||
if c.UserSetFlags["cpu"] {
|
||||
saved["cpu"] = c.CpuTestStatus
|
||||
}
|
||||
if c.UserSetFlags["memory"] {
|
||||
saved["memory"] = c.MemoryTestStatus
|
||||
}
|
||||
if c.UserSetFlags["disk"] {
|
||||
saved["disk"] = c.DiskTestStatus
|
||||
}
|
||||
if c.UserSetFlags["ut"] {
|
||||
saved["ut"] = c.UtTestStatus
|
||||
}
|
||||
if c.UserSetFlags["security"] {
|
||||
saved["security"] = c.SecurityTestStatus
|
||||
}
|
||||
if c.UserSetFlags["email"] {
|
||||
saved["email"] = c.EmailTestStatus
|
||||
}
|
||||
if c.UserSetFlags["backtrace"] {
|
||||
saved["backtrace"] = c.BacktraceStatus
|
||||
}
|
||||
if c.UserSetFlags["nt3"] {
|
||||
saved["nt3"] = c.Nt3Status
|
||||
}
|
||||
if c.UserSetFlags["speed"] {
|
||||
saved["speed"] = c.SpeedTestStatus
|
||||
}
|
||||
if c.UserSetFlags["ping"] {
|
||||
saved["ping"] = c.PingTestStatus
|
||||
}
|
||||
if c.UserSetFlags["tgdc"] {
|
||||
saved["tgdc"] = c.TgdcTestStatus
|
||||
}
|
||||
if c.UserSetFlags["web"] {
|
||||
saved["web"] = c.WebTestStatus
|
||||
}
|
||||
if c.UserSetFlags["cpum"] || c.UserSetFlags["cpu-method"] {
|
||||
saved["cpum"] = c.CpuTestMethod
|
||||
}
|
||||
if c.UserSetFlags["cput"] || c.UserSetFlags["cpu-thread"] {
|
||||
saved["cput"] = c.CpuTestThreadMode
|
||||
}
|
||||
if c.UserSetFlags["memorym"] || c.UserSetFlags["memory-method"] {
|
||||
saved["memorym"] = c.MemoryTestMethod
|
||||
}
|
||||
if c.UserSetFlags["diskm"] || c.UserSetFlags["disk-method"] {
|
||||
saved["diskm"] = c.DiskTestMethod
|
||||
}
|
||||
if c.UserSetFlags["diskp"] {
|
||||
saved["diskp"] = c.DiskTestPath
|
||||
}
|
||||
if c.UserSetFlags["diskmc"] {
|
||||
saved["diskmc"] = c.DiskMultiCheck
|
||||
}
|
||||
if c.UserSetFlags["nt3loc"] || c.UserSetFlags["nt3-location"] {
|
||||
saved["nt3loc"] = c.Nt3Location
|
||||
}
|
||||
if c.UserSetFlags["nt3t"] || c.UserSetFlags["nt3-type"] {
|
||||
saved["nt3t"] = c.Nt3CheckType
|
||||
}
|
||||
if c.UserSetFlags["spnum"] {
|
||||
saved["spnum"] = c.SpNum
|
||||
}
|
||||
|
||||
return saved
|
||||
}
|
||||
|
||||
// RestoreUserSetParams restores user-set parameters
|
||||
func (c *Config) RestoreUserSetParams(saved map[string]interface{}) {
|
||||
if val, ok := saved["basic"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.BasicStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["cpu"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.CpuTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["memory"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.MemoryTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["disk"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.DiskTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["ut"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.UtTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["security"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.SecurityTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["email"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.EmailTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["backtrace"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.BacktraceStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["nt3"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.Nt3Status = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["speed"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.SpeedTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["ping"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.PingTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["tgdc"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.TgdcTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["web"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.WebTestStatus = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["cpum"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.CpuTestMethod = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["cput"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.CpuTestThreadMode = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["memorym"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.MemoryTestMethod = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["diskm"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.DiskTestMethod = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["diskp"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.DiskTestPath = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["diskmc"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.DiskMultiCheck = boolVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["nt3loc"]; ok {
|
||||
if c.Choice != "10" {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.Nt3Location = strVal
|
||||
}
|
||||
}
|
||||
}
|
||||
if val, ok := saved["nt3t"]; ok {
|
||||
if strVal, ok := val.(string); ok {
|
||||
c.Nt3CheckType = strVal
|
||||
}
|
||||
}
|
||||
if val, ok := saved["spnum"]; ok {
|
||||
if intVal, ok := val.(int); ok {
|
||||
c.SpNum = intVal
|
||||
}
|
||||
}
|
||||
|
||||
c.ValidateParams()
|
||||
}
|
||||
|
||||
// ValidateParams validates parameter values
|
||||
func (c *Config) ValidateParams() {
|
||||
validCpuMethods := map[string]bool{"sysbench": true, "geekbench": true, "winsat": true}
|
||||
if !validCpuMethods[c.CpuTestMethod] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: CPU测试方法 '%s' 无效,使用默认值 'sysbench'\n", c.CpuTestMethod)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid CPU test method '%s', using default 'sysbench'\n", c.CpuTestMethod)
|
||||
}
|
||||
c.CpuTestMethod = "sysbench"
|
||||
}
|
||||
|
||||
validThreadModes := map[string]bool{"single": true, "multi": true}
|
||||
if !validThreadModes[c.CpuTestThreadMode] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: CPU线程模式 '%s' 无效,使用默认值 'multi'\n", c.CpuTestThreadMode)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid CPU thread mode '%s', using default 'multi'\n", c.CpuTestThreadMode)
|
||||
}
|
||||
c.CpuTestThreadMode = "multi"
|
||||
}
|
||||
|
||||
validMemoryMethods := map[string]bool{"stream": true, "sysbench": true, "dd": true, "winsat": true, "auto": true}
|
||||
if !validMemoryMethods[c.MemoryTestMethod] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: 内存测试方法 '%s' 无效,使用默认值 'stream'\n", c.MemoryTestMethod)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid memory test method '%s', using default 'stream'\n", c.MemoryTestMethod)
|
||||
}
|
||||
c.MemoryTestMethod = "stream"
|
||||
}
|
||||
|
||||
validDiskMethods := map[string]bool{"fio": true, "dd": true, "winsat": true}
|
||||
if !validDiskMethods[c.DiskTestMethod] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: 磁盘测试方法 '%s' 无效,使用默认值 'fio'\n", c.DiskTestMethod)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid disk test method '%s', using default 'fio'\n", c.DiskTestMethod)
|
||||
}
|
||||
c.DiskTestMethod = "fio"
|
||||
}
|
||||
|
||||
validNt3Locations := map[string]bool{"GZ": true, "SH": true, "BJ": true, "CD": true, "ALL": true}
|
||||
if !validNt3Locations[c.Nt3Location] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: NT3测试位置 '%s' 无效,使用默认值 'GZ'\n", c.Nt3Location)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid NT3 location '%s', using default 'GZ'\n", c.Nt3Location)
|
||||
}
|
||||
c.Nt3Location = "GZ"
|
||||
}
|
||||
|
||||
validNt3Types := map[string]bool{"both": true, "ipv4": true, "ipv6": true}
|
||||
if !validNt3Types[c.Nt3CheckType] {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: NT3测试类型 '%s' 无效,使用默认值 'ipv4'\n", c.Nt3CheckType)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid NT3 check type '%s', using default 'ipv4'\n", c.Nt3CheckType)
|
||||
}
|
||||
c.Nt3CheckType = "ipv4"
|
||||
}
|
||||
|
||||
if c.SpNum < 0 {
|
||||
if c.Language == "zh" {
|
||||
fmt.Printf("警告: 测速节点数量 '%d' 无效,使用默认值 2\n", c.SpNum)
|
||||
} else {
|
||||
fmt.Printf("Warning: Invalid speed test node count '%d', using default 2\n", c.SpNum)
|
||||
}
|
||||
c.SpNum = 2
|
||||
}
|
||||
|
||||
validLanguages := map[string]bool{"zh": true, "en": true}
|
||||
if !validLanguages[c.Language] {
|
||||
fmt.Printf("Warning: Invalid language '%s', using default 'zh'\n", c.Language)
|
||||
c.Language = "zh"
|
||||
}
|
||||
}
|
||||
529
internal/runner/runner.go
Normal file
529
internal/runner/runner.go
Normal file
@@ -0,0 +1,529 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
"github.com/oneclickvirt/ecs/internal/tests"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
"github.com/oneclickvirt/pingtest/pt"
|
||||
"github.com/oneclickvirt/portchecker/email"
|
||||
)
|
||||
|
||||
// RunChineseTests runs all tests in Chinese mode
|
||||
func RunChineseTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex, infoMutex *sync.Mutex) {
|
||||
*output = RunBasicTests(preCheck, config, basicInfo, securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = RunCPUTest(config, *output, tempOutput, outputMutex)
|
||||
*output = RunMemoryTest(config, *output, tempOutput, outputMutex)
|
||||
*output = RunDiskTest(config, *output, tempOutput, outputMutex)
|
||||
if config.OnlyIpInfoCheck && !config.BasicStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = RunIpInfoCheck(config, *output, tempOutput, outputMutex)
|
||||
}
|
||||
if config.UtTestStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" && !config.OnlyChinaTest {
|
||||
wg1.Add(1)
|
||||
go func() {
|
||||
defer wg1.Done()
|
||||
result := tests.MediaTest(config.Language)
|
||||
infoMutex.Lock()
|
||||
*mediaInfo = result
|
||||
infoMutex.Unlock()
|
||||
}()
|
||||
}
|
||||
if config.EmailTestStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
wg2.Add(1)
|
||||
go func() {
|
||||
defer wg2.Done()
|
||||
result := email.EmailCheck()
|
||||
infoMutex.Lock()
|
||||
*emailInfo = result
|
||||
infoMutex.Unlock()
|
||||
}()
|
||||
}
|
||||
if (config.OnlyChinaTest || config.PingTestStatus) && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
wg3.Add(1)
|
||||
go func() {
|
||||
defer wg3.Done()
|
||||
result := pt.PingTest()
|
||||
infoMutex.Lock()
|
||||
*ptInfo = result
|
||||
infoMutex.Unlock()
|
||||
}()
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex, infoMutex)
|
||||
*output = RunSecurityTests(config, *securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex, infoMutex)
|
||||
}
|
||||
if runtime.GOOS != "windows" && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = RunNetworkTests(config, wg3, ptInfo, *output, tempOutput, outputMutex, infoMutex)
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = RunSpeedTests(config, *output, tempOutput, outputMutex)
|
||||
}
|
||||
*output = AppendTimeInfo(config, *output, tempOutput, startTime, outputMutex)
|
||||
}
|
||||
|
||||
// RunEnglishTests runs all tests in English mode
|
||||
func RunEnglishTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex, infoMutex *sync.Mutex) {
|
||||
*output = RunBasicTests(preCheck, config, basicInfo, securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = RunCPUTest(config, *output, tempOutput, outputMutex)
|
||||
*output = RunMemoryTest(config, *output, tempOutput, outputMutex)
|
||||
*output = RunDiskTest(config, *output, tempOutput, outputMutex)
|
||||
if config.OnlyIpInfoCheck && !config.BasicStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = RunIpInfoCheck(config, *output, tempOutput, outputMutex)
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
if config.UtTestStatus {
|
||||
wg1.Add(1)
|
||||
go func() {
|
||||
defer wg1.Done()
|
||||
result := tests.MediaTest(config.Language)
|
||||
infoMutex.Lock()
|
||||
*mediaInfo = result
|
||||
infoMutex.Unlock()
|
||||
}()
|
||||
}
|
||||
if config.EmailTestStatus {
|
||||
wg2.Add(1)
|
||||
go func() {
|
||||
defer wg2.Done()
|
||||
result := email.EmailCheck()
|
||||
infoMutex.Lock()
|
||||
*emailInfo = result
|
||||
infoMutex.Unlock()
|
||||
}()
|
||||
}
|
||||
*output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex, infoMutex)
|
||||
*output = RunSecurityTests(config, *securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex, infoMutex)
|
||||
*output = RunEnglishNetworkTests(config, wg3, ptInfo, *output, tempOutput, outputMutex)
|
||||
*output = RunEnglishSpeedTests(config, *output, tempOutput, outputMutex)
|
||||
}
|
||||
*output = AppendTimeInfo(config, *output, tempOutput, startTime, outputMutex)
|
||||
}
|
||||
|
||||
// RunIpInfoCheck performs IP info check
|
||||
func RunIpInfoCheck(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
var ipinfo string
|
||||
tests.IPV4, tests.IPV6, ipinfo = utils.OnlyBasicsIpInfo(config.Language)
|
||||
if ipinfo != "" {
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("IP信息", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("IP-Information", config.Width)
|
||||
}
|
||||
fmt.Printf("%s", ipinfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunBasicTests runs basic system tests
|
||||
func RunBasicTests(preCheck utils.NetCheckResult, config *params.Config, basicInfo, securityInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
utils.PrintHead(config.Language, config.Width, config.EcsVersion)
|
||||
if config.BasicStatus || config.SecurityTestStatus {
|
||||
if config.BasicStatus {
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("系统基础信息", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("System-Basic-Information", config.Width)
|
||||
}
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType == "DualStack" {
|
||||
tests.IPV4, tests.IPV6, *basicInfo, *securityInfo, config.Nt3CheckType = utils.BasicsAndSecurityCheck(config.Language, config.Nt3CheckType, config.SecurityTestStatus)
|
||||
} else if preCheck.Connected && preCheck.StackType == "IPv4" {
|
||||
tests.IPV4, tests.IPV6, *basicInfo, *securityInfo, config.Nt3CheckType = utils.BasicsAndSecurityCheck(config.Language, "ipv4", config.SecurityTestStatus)
|
||||
} else if preCheck.Connected && preCheck.StackType == "IPv6" {
|
||||
tests.IPV4, tests.IPV6, *basicInfo, *securityInfo, config.Nt3CheckType = utils.BasicsAndSecurityCheck(config.Language, "ipv6", config.SecurityTestStatus)
|
||||
} else {
|
||||
tests.IPV4, tests.IPV6, *basicInfo, *securityInfo, config.Nt3CheckType = utils.BasicsAndSecurityCheck(config.Language, "", false)
|
||||
config.SecurityTestStatus = false
|
||||
}
|
||||
if config.BasicStatus {
|
||||
fmt.Printf("%s", *basicInfo)
|
||||
} else if (config.Input == "6" || config.Input == "9") && config.SecurityTestStatus {
|
||||
scanner := bufio.NewScanner(strings.NewReader(*basicInfo))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, "IPV") {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunCPUTest runs CPU test
|
||||
func RunCPUTest(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.CpuTestStatus {
|
||||
realTestMethod, res := tests.CpuTest(config.Language, config.CpuTestMethod, config.CpuTestThreadMode)
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU测试-通过%s测试", realTestMethod), config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU-Test--%s-Method", realTestMethod), config.Width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunMemoryTest runs memory test
|
||||
func RunMemoryTest(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.MemoryTestStatus {
|
||||
realTestMethod, res := tests.MemoryTest(config.Language, config.MemoryTestMethod)
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("内存测试-通过%s测试", realTestMethod), config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Memory-Test--%s-Method", realTestMethod), config.Width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunDiskTest runs disk test
|
||||
func RunDiskTest(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.DiskTestStatus && config.AutoChangeDiskMethod {
|
||||
realTestMethod, res := tests.DiskTest(config.Language, config.DiskTestMethod, config.DiskTestPath, config.DiskMultiCheck, config.AutoChangeDiskMethod)
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", realTestMethod), config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", realTestMethod), config.Width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
} else if config.DiskTestStatus && !config.AutoChangeDiskMethod {
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", "dd"), config.Width)
|
||||
_, res := tests.DiskTest(config.Language, "dd", config.DiskTestPath, config.DiskMultiCheck, config.AutoChangeDiskMethod)
|
||||
fmt.Print(res)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", "fio"), config.Width)
|
||||
_, res = tests.DiskTest(config.Language, "fio", config.DiskTestPath, config.DiskMultiCheck, config.AutoChangeDiskMethod)
|
||||
fmt.Print(res)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", "dd"), config.Width)
|
||||
_, res := tests.DiskTest(config.Language, "dd", config.DiskTestPath, config.DiskMultiCheck, config.AutoChangeDiskMethod)
|
||||
fmt.Print(res)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", "fio"), config.Width)
|
||||
_, res = tests.DiskTest(config.Language, "fio", config.DiskTestPath, config.DiskMultiCheck, config.AutoChangeDiskMethod)
|
||||
fmt.Print(res)
|
||||
}
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunStreamingTests runs platform unlock tests
|
||||
func RunStreamingTests(config *params.Config, wg1 *sync.WaitGroup, mediaInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.UtTestStatus && (config.Language == "zh" && !config.OnlyChinaTest || config.Language == "en") {
|
||||
wg1.Wait()
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("跨国平台解锁", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("Cross-Border-Platform-Unlock", config.Width)
|
||||
}
|
||||
infoMutex.Lock()
|
||||
info := *mediaInfo
|
||||
infoMutex.Unlock()
|
||||
fmt.Printf("%s", info)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunSecurityTests runs security tests
|
||||
func RunSecurityTests(config *params.Config, securityInfo, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.SecurityTestStatus {
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("IP质量检测", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("IP-Quality-Check", config.Width)
|
||||
}
|
||||
fmt.Printf("%s", securityInfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunEmailTests runs email port tests
|
||||
func RunEmailTests(config *params.Config, wg2 *sync.WaitGroup, emailInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.EmailTestStatus {
|
||||
wg2.Wait()
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("邮件端口检测", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("Email-Port-Check", config.Width)
|
||||
}
|
||||
infoMutex.Lock()
|
||||
info := *emailInfo
|
||||
infoMutex.Unlock()
|
||||
fmt.Println(info)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunNetworkTests runs network tests (Chinese mode)
|
||||
func RunNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.BacktraceStatus && !config.OnlyChinaTest {
|
||||
utils.PrintCenteredTitle("上游及回程线路检测", config.Width)
|
||||
tests.UpstreamsCheck(config.Language)
|
||||
}
|
||||
if config.Nt3Status && !config.OnlyChinaTest {
|
||||
utils.PrintCenteredTitle("三网回程路由检测", config.Width)
|
||||
tests.NextTrace3Check(config.Language, config.Nt3Location, config.Nt3CheckType)
|
||||
}
|
||||
infoMutex.Lock()
|
||||
info := *ptInfo
|
||||
infoMutex.Unlock()
|
||||
if config.OnlyChinaTest && info != "" {
|
||||
wg3.Wait()
|
||||
utils.PrintCenteredTitle("PING值检测", config.Width)
|
||||
fmt.Println(info)
|
||||
}
|
||||
if config.PingTestStatus && info != "" {
|
||||
wg3.Wait()
|
||||
utils.PrintCenteredTitle("PING值检测", config.Width)
|
||||
fmt.Println(info)
|
||||
if config.TgdcTestStatus {
|
||||
fmt.Println(pt.TelegramDCTest())
|
||||
}
|
||||
if config.WebTestStatus {
|
||||
fmt.Println(pt.WebsiteTest())
|
||||
}
|
||||
}
|
||||
if !config.OnlyChinaTest && !config.PingTestStatus && (config.TgdcTestStatus || config.WebTestStatus) {
|
||||
utils.PrintCenteredTitle("PING值检测", config.Width)
|
||||
if config.TgdcTestStatus {
|
||||
fmt.Println(pt.TelegramDCTest())
|
||||
}
|
||||
if config.WebTestStatus {
|
||||
fmt.Println(pt.WebsiteTest())
|
||||
}
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunSpeedTests runs speed tests (Chinese mode)
|
||||
func RunSpeedTests(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.SpeedTestStatus {
|
||||
utils.PrintCenteredTitle("就近节点测速", config.Width)
|
||||
tests.ShowHead(config.Language)
|
||||
if config.Choice == "1" || !config.MenuMode {
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "global", 2, config.Language)
|
||||
tests.CustomSP("net", "cu", config.SpNum, config.Language)
|
||||
tests.CustomSP("net", "ct", config.SpNum, config.Language)
|
||||
tests.CustomSP("net", "cmcc", config.SpNum, config.Language)
|
||||
} else if config.Choice == "2" || config.Choice == "3" || config.Choice == "4" || config.Choice == "5" {
|
||||
// 中文模式:就近测速 + 三网各1个 + Other 1个(带回退)
|
||||
if config.Language == "zh" {
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "other", 1, config.Language)
|
||||
tests.CustomSP("net", "cu", 1, config.Language)
|
||||
tests.CustomSP("net", "ct", 1, config.Language)
|
||||
tests.CustomSP("net", "cmcc", 1, config.Language)
|
||||
} else {
|
||||
// 英文模式:保持原有逻辑,测4个global节点
|
||||
tests.CustomSP("net", "global", 4, config.Language)
|
||||
}
|
||||
} else if config.Choice == "6" {
|
||||
tests.CustomSP("net", "global", 11, config.Language)
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunEnglishNetworkTests runs network tests (English mode)
|
||||
func RunEnglishNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.TgdcTestStatus || config.WebTestStatus {
|
||||
utils.PrintCenteredTitle("PING-Test", config.Width)
|
||||
if config.TgdcTestStatus {
|
||||
fmt.Println(pt.TelegramDCTest())
|
||||
}
|
||||
if config.WebTestStatus {
|
||||
fmt.Println(pt.WebsiteTest())
|
||||
}
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// RunEnglishSpeedTests runs speed tests (English mode)
|
||||
func RunEnglishSpeedTests(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if config.SpeedTestStatus {
|
||||
utils.PrintCenteredTitle("Speed-Test", config.Width)
|
||||
tests.ShowHead(config.Language)
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "global", -1, config.Language)
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// AppendTimeInfo appends timing information
|
||||
func AppendTimeInfo(config *params.Config, output, tempOutput string, startTime time.Time, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
endTime := time.Now()
|
||||
duration := endTime.Sub(startTime)
|
||||
minutes := int(duration.Minutes())
|
||||
seconds := int(duration.Seconds()) % 60
|
||||
currentTime := time.Now().Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
return utils.PrintAndCapture(func() {
|
||||
utils.PrintCenteredTitle("", config.Width)
|
||||
if config.Language == "zh" {
|
||||
fmt.Printf("花费 : %d 分 %d 秒\n", minutes, seconds)
|
||||
fmt.Printf("时间 : %s\n", currentTime)
|
||||
} else {
|
||||
fmt.Printf("Cost Time : %d min %d sec\n", minutes, seconds)
|
||||
fmt.Printf("Current Time : %s\n", currentTime)
|
||||
}
|
||||
utils.PrintCenteredTitle("", config.Width)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
// HandleSignalInterrupt handles interrupt signals
|
||||
func HandleSignalInterrupt(sig chan os.Signal, config *params.Config, startTime *time.Time, output *string, tempOutput string, uploadDone chan bool, outputMutex *sync.Mutex) {
|
||||
select {
|
||||
case <-sig:
|
||||
if !config.Finish {
|
||||
endTime := time.Now()
|
||||
duration := endTime.Sub(*startTime)
|
||||
minutes := int(duration.Minutes())
|
||||
seconds := int(duration.Seconds()) % 60
|
||||
currentTime := time.Now().Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
outputMutex.Lock()
|
||||
timeInfo := utils.PrintAndCapture(func() {
|
||||
utils.PrintCenteredTitle("", config.Width)
|
||||
if config.Language == "zh" {
|
||||
fmt.Printf("花费 : %d 分 %d 秒\n", minutes, seconds)
|
||||
fmt.Printf("时间 : %s\n", currentTime)
|
||||
} else {
|
||||
fmt.Printf("Cost Time : %d min %d sec\n", minutes, seconds)
|
||||
fmt.Printf("Current Time : %s\n", currentTime)
|
||||
}
|
||||
utils.PrintCenteredTitle("", config.Width)
|
||||
}, "", "")
|
||||
*output += timeInfo
|
||||
finalOutput := *output
|
||||
outputMutex.Unlock()
|
||||
resultChan := make(chan struct {
|
||||
httpURL string
|
||||
httpsURL string
|
||||
}, 1)
|
||||
if config.EnableUpload {
|
||||
// 使用context来控制上传goroutine
|
||||
uploadCtx, uploadCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer uploadCancel()
|
||||
|
||||
go func() {
|
||||
httpURL, httpsURL := utils.ProcessAndUpload(finalOutput, config.FilePath, config.EnableUpload, config.Language)
|
||||
select {
|
||||
case resultChan <- struct {
|
||||
httpURL string
|
||||
httpsURL string
|
||||
}{httpURL, httpsURL}:
|
||||
case <-uploadCtx.Done():
|
||||
// 上传被取消或超时,直接返回
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
uploadCancel() // 成功完成,取消context
|
||||
if result.httpURL != "" || result.httpsURL != "" {
|
||||
if config.Language == "en" {
|
||||
fmt.Printf("Upload successfully!\nHttp URL: %s\nHttps URL: %s\n", result.httpURL, result.httpsURL)
|
||||
} else {
|
||||
fmt.Printf("上传成功!\nHttp URL: %s\nHttps URL: %s\n", result.httpURL, result.httpsURL)
|
||||
}
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
fmt.Scanln()
|
||||
}
|
||||
os.Exit(0)
|
||||
case <-uploadCtx.Done():
|
||||
if config.Language == "en" {
|
||||
fmt.Println("Upload timeout, program exit")
|
||||
} else {
|
||||
fmt.Println("上传超时,程序退出")
|
||||
}
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
fmt.Scanln()
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
|
||||
fmt.Println("Press Enter to exit...")
|
||||
fmt.Scanln()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleUploadResults handles uploading results
|
||||
func HandleUploadResults(config *params.Config, output string) {
|
||||
httpURL, httpsURL := utils.ProcessAndUpload(output, config.FilePath, config.EnableUpload, config.Language)
|
||||
if httpURL != "" || httpsURL != "" {
|
||||
if config.Language == "en" {
|
||||
fmt.Printf("Upload successfully!\nHttp URL: %s\nHttps URL: %s\n", httpURL, httpsURL)
|
||||
fmt.Println("Each Test Benchmark: https://bash.spiritlhl.net/ecsguide")
|
||||
} else {
|
||||
fmt.Printf("上传成功!\nHttp URL: %s\nHttps URL: %s\n", httpURL, httpsURL)
|
||||
fmt.Println("每项测试基准见: https://bash.spiritlhl.net/ecsguide")
|
||||
}
|
||||
}
|
||||
}
|
||||
56
internal/tests/cpu.go
Normal file
56
internal/tests/cpu.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/cputest/cpu"
|
||||
)
|
||||
|
||||
func CpuTest(language, testMethod, testThread string) (realTestMethod, res string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] CpuTest panic: %v\n", r)
|
||||
res = fmt.Sprintf("\nCPU test failed: %v\n", r)
|
||||
realTestMethod = "error"
|
||||
}
|
||||
}()
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
if testMethod != "winsat" && testMethod != "" {
|
||||
// res = "Detected host is Windows, using Winsat for testing.\n"
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
res += cpu.WinsatTest(language, testThread)
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "sysbench":
|
||||
res = cpu.SysBenchTest(language, testThread)
|
||||
if res == "" {
|
||||
// res = "Sysbench test failed, switching to Geekbench for testing.\n"
|
||||
realTestMethod = "geekbench"
|
||||
res += cpu.GeekBenchTest(language, testThread)
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
case "geekbench":
|
||||
res = cpu.GeekBenchTest(language, testThread)
|
||||
if res == "" {
|
||||
// res = "Geekbench test failed, switching to Sysbench for testing.\n"
|
||||
realTestMethod = "sysbench"
|
||||
res += cpu.SysBenchTest(language, testThread)
|
||||
} else {
|
||||
realTestMethod = "geekbench"
|
||||
}
|
||||
default:
|
||||
res = "Invalid test method specified.\n"
|
||||
realTestMethod = "null"
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
51
internal/tests/disk.go
Normal file
51
internal/tests/disk.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/disktest/disk"
|
||||
)
|
||||
|
||||
func DiskTest(language, testMethod, testPath string, isMultiCheck bool, autoChange bool) (realTestMethod, res string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] DiskTest panic: %v\n", r)
|
||||
res = fmt.Sprintf("\nDisk test failed: %v\n", r)
|
||||
realTestMethod = "error"
|
||||
}
|
||||
}()
|
||||
|
||||
switch testMethod {
|
||||
case "fio":
|
||||
res = disk.FioTest(language, isMultiCheck, testPath)
|
||||
if res == "" && autoChange {
|
||||
res += disk.DDTest(language, isMultiCheck, testPath)
|
||||
realTestMethod = "dd"
|
||||
} else {
|
||||
realTestMethod = "fio"
|
||||
}
|
||||
case "dd":
|
||||
res = disk.DDTest(language, isMultiCheck, testPath)
|
||||
if res == "" && autoChange {
|
||||
res += disk.FioTest(language, isMultiCheck, testPath)
|
||||
realTestMethod = "fio"
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
default:
|
||||
if runtime.GOOS == "windows" {
|
||||
realTestMethod = "winsat"
|
||||
res = disk.WinsatTest(language, isMultiCheck, testPath)
|
||||
} else {
|
||||
res = disk.DDTest(language, isMultiCheck, testPath)
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
233
internal/tests/memory.go
Normal file
233
internal/tests/memory.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/memorytest/memory"
|
||||
)
|
||||
|
||||
func MemoryTest(language, testMethod string) (realTestMethod, res string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] MemoryTest panic: %v\n", r)
|
||||
res = fmt.Sprintf("\nMemory test failed: %v\n", r)
|
||||
realTestMethod = "error"
|
||||
}
|
||||
}()
|
||||
|
||||
testMethod = strings.ToLower(testMethod)
|
||||
if testMethod == "" {
|
||||
testMethod = "auto"
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
switch testMethod {
|
||||
case "stream":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "dd":
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
case "sysbench":
|
||||
// Windows下不支持sysbench,使用stream → winsat → dd
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "auto":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "winsat":
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
default:
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WinsatTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "stream":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "dd":
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
case "sysbench":
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
case "auto":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "winsat":
|
||||
// winsat 仅 Windows 支持,非 Windows fallback 到 stream → sysbench → dd
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
default:
|
||||
res = "Unsupported test method"
|
||||
realTestMethod = ""
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
98
internal/tests/nexttrace.go
Normal file
98
internal/tests/nexttrace.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/nt3/nt"
|
||||
)
|
||||
|
||||
func NextTrace3Check(language, nt3Location, nt3CheckType string) {
|
||||
// 先检查 ICMP 权限
|
||||
conn, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if err != nil {
|
||||
// 没有权限,显示友好提示并跳过
|
||||
if language == "zh" {
|
||||
fmt.Println("路由追踪测试需要 root 权限或 CAP_NET_RAW 能力,已跳过")
|
||||
fmt.Fprintf(os.Stderr, "[WARN] ICMP权限不足: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("Route tracing test requires root privileges or CAP_NET_RAW capability, skipped")
|
||||
fmt.Fprintf(os.Stderr, "[WARN] Insufficient ICMP permission: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
conn.Close()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("路由追踪测试出现错误,已跳过")
|
||||
fmt.Fprintf(os.Stderr, "[WARN] 路由追踪panic: %v\n", r)
|
||||
} else {
|
||||
fmt.Println("Route tracing test failed, skipped")
|
||||
fmt.Fprintf(os.Stderr, "[WARN] Route tracing panic: %v\n", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
resultChan := make(chan nt.TraceResult, 100)
|
||||
errorOccurred := false
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errorOccurred = true
|
||||
resultChan <- nt.TraceResult{
|
||||
Index: -1,
|
||||
ISPName: "Error",
|
||||
Output: []string{fmt.Sprintf("Route tracing error: %v", r)},
|
||||
}
|
||||
close(resultChan)
|
||||
}
|
||||
}()
|
||||
nt.TraceRoute(language, nt3Location, nt3CheckType, resultChan)
|
||||
}()
|
||||
for result := range resultChan {
|
||||
if result.Index == -1 {
|
||||
for index, res := range result.Output {
|
||||
res = strings.TrimSpace(res)
|
||||
if res != "" && index == 0 {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if result.ISPName == "Error" {
|
||||
if language == "zh" {
|
||||
fmt.Println("路由追踪测试失败(可能因为权限不足),已跳过")
|
||||
} else {
|
||||
fmt.Println("Route tracing test failed (possibly due to insufficient permissions), skipped")
|
||||
}
|
||||
for _, res := range result.Output {
|
||||
res = strings.TrimSpace(res)
|
||||
if res != "" {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] %s\n", res)
|
||||
}
|
||||
}
|
||||
errorOccurred = true
|
||||
continue
|
||||
}
|
||||
for _, res := range result.Output {
|
||||
res = strings.TrimSpace(res)
|
||||
if res == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(res, "ICMP") {
|
||||
fmt.Print(res)
|
||||
} else {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
if errorOccurred {
|
||||
if language == "zh" {
|
||||
fmt.Println("提示: 路由追踪需要 root 权限或 CAP_NET_RAW 能力")
|
||||
} else {
|
||||
fmt.Println("Hint: Route tracing requires root privileges or CAP_NET_RAW capability")
|
||||
}
|
||||
}
|
||||
}
|
||||
284
internal/tests/speed.go
Normal file
284
internal/tests/speed.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/privatespeedtest/pst"
|
||||
"github.com/oneclickvirt/speedtest/model"
|
||||
"github.com/oneclickvirt/speedtest/sp"
|
||||
)
|
||||
|
||||
func ShowHead(language string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] ShowHead panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
sp.ShowHead(language)
|
||||
}
|
||||
|
||||
func NearbySP() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] NearbySP panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.NearbySpeedTest()
|
||||
} else {
|
||||
sp.OfficialNearbySpeedTest()
|
||||
}
|
||||
}
|
||||
|
||||
// formatString 格式化字符串到指定宽度
|
||||
func formatString(s string, width int) string {
|
||||
return fmt.Sprintf("%-*s", width, s)
|
||||
}
|
||||
|
||||
// printTableRow 打印表格行
|
||||
func printTableRow(result pst.SpeedTestResult) {
|
||||
location := result.City
|
||||
if result.CarrierType != "" {
|
||||
carrier := result.CarrierType
|
||||
switch carrier {
|
||||
case "Telecom":
|
||||
carrier = "电信"
|
||||
case "Unicom":
|
||||
carrier = "联通"
|
||||
case "Mobile":
|
||||
carrier = "移动"
|
||||
case "Other":
|
||||
carrier = "其他"
|
||||
}
|
||||
location = fmt.Sprintf("%s%s", carrier, result.City)
|
||||
}
|
||||
if len(location) > 15 {
|
||||
location = location[:15]
|
||||
}
|
||||
upload := "N/A"
|
||||
if result.UploadMbps > 0 {
|
||||
upload = fmt.Sprintf("%.2f Mbps", result.UploadMbps)
|
||||
}
|
||||
download := "N/A"
|
||||
if result.DownloadMbps > 0 {
|
||||
download = fmt.Sprintf("%.2f Mbps", result.DownloadMbps)
|
||||
}
|
||||
latency := fmt.Sprintf("%.2f ms", result.PingLatency.Seconds()*1000)
|
||||
packetLoss := "N/A"
|
||||
fmt.Print(formatString(location, 15))
|
||||
fmt.Print(formatString(upload, 16))
|
||||
fmt.Print(formatString(download, 16))
|
||||
fmt.Print(formatString(latency, 16))
|
||||
fmt.Print(formatString(packetLoss, 16))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// privateSpeedTest 使用 privatespeedtest 进行单个运营商测速
|
||||
// operator 参数:只支持 "cmcc"、"cu"、"ct"、"other"
|
||||
// 返回值:实际测试的节点数量和错误信息
|
||||
func privateSpeedTest(num int, operator string) (int, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privateSpeedTest panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
*pst.NoProgress = true
|
||||
*pst.Quiet = true
|
||||
*pst.NoHeader = true
|
||||
*pst.NoProjectURL = true
|
||||
// 加载服务器列表
|
||||
serverList, err := pst.LoadServerList()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("加载自定义服务器列表失败")
|
||||
}
|
||||
// 使用三网测速模式(每个运营商选择指定数量的最低延迟节点)
|
||||
serversPerISP := num
|
||||
if serversPerISP <= 0 || serversPerISP > 5 {
|
||||
serversPerISP = 2
|
||||
}
|
||||
// 单个运营商测速:先过滤服务器列表
|
||||
var carrierType string
|
||||
switch strings.ToLower(operator) {
|
||||
case "cmcc":
|
||||
carrierType = "Mobile"
|
||||
case "cu":
|
||||
carrierType = "Unicom"
|
||||
case "ct":
|
||||
carrierType = "Telecom"
|
||||
case "other":
|
||||
carrierType = "Other"
|
||||
default:
|
||||
return 0, fmt.Errorf("不支持的运营商类型: %s", operator)
|
||||
}
|
||||
// 过滤出指定运营商的服务器
|
||||
filteredServers := pst.FilterServersByISP(serverList.Servers, carrierType)
|
||||
// 先找足够多的候选服务器用于去重(找 serversPerISP * 3 个,确保去重后还能剩下足够的服务器)
|
||||
candidateCount := serversPerISP * 3
|
||||
if candidateCount > len(filteredServers) {
|
||||
candidateCount = len(filteredServers)
|
||||
}
|
||||
// 使用 FindBestServers 选择最佳服务器
|
||||
candidateServers, err := pst.FindBestServers(
|
||||
filteredServers,
|
||||
candidateCount, // 选择更多候选节点用于去重
|
||||
5*time.Second, // ping 超时
|
||||
true, // 显示进度条
|
||||
true, // 静默
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("分组查找失败")
|
||||
}
|
||||
// 去重:确保同一运营商内城市不重复
|
||||
seenCities := make(map[string]bool)
|
||||
var bestServers []pst.ServerWithLatencyInfo
|
||||
// 保留更多备用节点,以应对测速失败的情况(保留 serversPerISP * 2 个备用节点)
|
||||
maxBackupServers := serversPerISP * 2
|
||||
for _, serverInfo := range candidateServers {
|
||||
city := serverInfo.Server.City
|
||||
if city == "" {
|
||||
city = "Unknown"
|
||||
}
|
||||
if !seenCities[city] {
|
||||
seenCities[city] = true
|
||||
bestServers = append(bestServers, serverInfo)
|
||||
// 去重后保留足够的备用节点
|
||||
if len(bestServers) >= maxBackupServers {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bestServers) == 0 {
|
||||
return 0, fmt.Errorf("去重后没有可用的服务器")
|
||||
}
|
||||
// 执行测速并逐个打印结果(不打印表头)
|
||||
// 统计成功输出的节点数
|
||||
successCount := 0
|
||||
for i, serverInfo := range bestServers {
|
||||
// 如果已经成功输出了足够的节点,则停止测试
|
||||
if successCount >= serversPerISP {
|
||||
break
|
||||
}
|
||||
result := pst.RunSpeedTest(
|
||||
serverInfo.Server,
|
||||
false, // 不禁用下载测试
|
||||
false, // 不禁用上传测试
|
||||
6, // 并发线程数
|
||||
12*time.Second, // 超时时间
|
||||
&serverInfo,
|
||||
false, // 不显示进度条
|
||||
)
|
||||
// 只要测试成功且有任意一个速度值有效,就输出结果(部分成功也显示)
|
||||
if result.Success && (result.UploadMbps > 0 || result.DownloadMbps > 0) {
|
||||
printTableRow(result)
|
||||
// 只有上传和下载都成功时才计入成功数
|
||||
if result.UploadMbps > 0 && result.DownloadMbps > 0 {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
// 在测试之间暂停(如果还需要继续测试的话)
|
||||
if successCount < serversPerISP && i < len(bestServers)-1 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
// 返回实际成功输出的节点数量
|
||||
return successCount, nil
|
||||
}
|
||||
|
||||
// privateSpeedTestWithFallback 使用私有测速,如果失败则回退到 global 节点
|
||||
// 主要用于 Other 类型的测速
|
||||
func privateSpeedTestWithFallback(num int, operator, language string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privateSpeedTestWithFallback panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 先尝试私有节点测速
|
||||
testedCount, err := privateSpeedTest(num, operator)
|
||||
if err != nil || testedCount == 0 {
|
||||
// 私有节点失败,回退到 global 节点
|
||||
var url, parseType string
|
||||
url = model.NetGlobal
|
||||
parseType = "id"
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.CustomSpeedTest(url, parseType, num, language)
|
||||
} else {
|
||||
sp.OfficialCustomSpeedTest(url, parseType, num, language)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CustomSP(platform, operator string, num int, language string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] CustomSP panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 对于三网测速(cmcc、cu、ct)和 other,优先使用 privatespeedtest 进行私有测速
|
||||
opLower := strings.ToLower(operator)
|
||||
if opLower == "cmcc" || opLower == "cu" || opLower == "ct" || opLower == "other" {
|
||||
testedCount, err := privateSpeedTest(num, opLower)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privatespeedtest failed: %v\n", err)
|
||||
// 全部失败,继续使用原有的公共节点兜底方案
|
||||
} else if testedCount >= num {
|
||||
// 私有节点测速成功且数量达标,直接返回
|
||||
return
|
||||
} else if testedCount > 0 {
|
||||
// 部分私有节点测速成功,但数量不足,用公共节点补充
|
||||
fmt.Fprintf(os.Stderr, "[INFO] 私有节点仅测试了 %d 个,补充 %d 个公共节点\n", testedCount, num-testedCount)
|
||||
num = num - testedCount // 只测剩余数量的公共节点
|
||||
// 继续执行下面的公共节点测速逻辑
|
||||
} else {
|
||||
// testedCount == 0,继续使用公共节点
|
||||
}
|
||||
}
|
||||
|
||||
var url, parseType string
|
||||
if strings.ToLower(platform) == "cn" {
|
||||
if strings.ToLower(operator) == "cmcc" {
|
||||
url = model.CnCMCC
|
||||
} else if strings.ToLower(operator) == "cu" {
|
||||
url = model.CnCU
|
||||
} else if strings.ToLower(operator) == "ct" {
|
||||
url = model.CnCT
|
||||
} else if strings.ToLower(operator) == "hk" {
|
||||
url = model.CnHK
|
||||
} else if strings.ToLower(operator) == "tw" {
|
||||
url = model.CnTW
|
||||
} else if strings.ToLower(operator) == "jp" {
|
||||
url = model.CnJP
|
||||
} else if strings.ToLower(operator) == "sg" {
|
||||
url = model.CnSG
|
||||
}
|
||||
parseType = "url"
|
||||
} else if strings.ToLower(platform) == "net" {
|
||||
if strings.ToLower(operator) == "cmcc" {
|
||||
url = model.NetCMCC
|
||||
} else if strings.ToLower(operator) == "cu" {
|
||||
url = model.NetCU
|
||||
} else if strings.ToLower(operator) == "ct" {
|
||||
url = model.NetCT
|
||||
} else if strings.ToLower(operator) == "hk" {
|
||||
url = model.NetHK
|
||||
} else if strings.ToLower(operator) == "tw" {
|
||||
url = model.NetTW
|
||||
} else if strings.ToLower(operator) == "jp" {
|
||||
url = model.NetJP
|
||||
} else if strings.ToLower(operator) == "sg" {
|
||||
url = model.NetSG
|
||||
} else if strings.ToLower(operator) == "global" || strings.ToLower(operator) == "other" {
|
||||
// other 类型回退到 global 节点
|
||||
url = model.NetGlobal
|
||||
}
|
||||
parseType = "id"
|
||||
}
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.CustomSpeedTest(url, parseType, num, language)
|
||||
} else {
|
||||
sp.OfficialCustomSpeedTest(url, parseType, num, language)
|
||||
}
|
||||
}
|
||||
35
internal/tests/unlock.go
Normal file
35
internal/tests/unlock.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/oneclickvirt/UnlockTests/executor"
|
||||
"github.com/oneclickvirt/UnlockTests/utils"
|
||||
"github.com/oneclickvirt/defaultset"
|
||||
)
|
||||
|
||||
func MediaTest(language string) string {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] MediaTest panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var res string
|
||||
readStatus := executor.ReadSelect(language, "0")
|
||||
if !readStatus {
|
||||
return ""
|
||||
}
|
||||
if executor.IPV4 {
|
||||
res += defaultset.Blue("IPV4:") + "\n"
|
||||
res += executor.RunTests(utils.Ipv4HttpClient, "ipv4", language, false)
|
||||
return res
|
||||
}
|
||||
if executor.IPV6 {
|
||||
res += defaultset.Blue("IPV6:") + "\n"
|
||||
res += executor.RunTests(utils.Ipv6HttpClient, "ipv6", language, false)
|
||||
return res
|
||||
}
|
||||
return ""
|
||||
}
|
||||
94
internal/tests/upstreams.go
Normal file
94
internal/tests/upstreams.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/UnlockTests/executor"
|
||||
bgptools "github.com/oneclickvirt/backtrace/bgptools"
|
||||
backtrace "github.com/oneclickvirt/backtrace/bk"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
)
|
||||
|
||||
type IpInfo struct {
|
||||
Ip string `json:"ip"`
|
||||
City string `json:"city"`
|
||||
Region string `json:"region"`
|
||||
Country string `json:"country"`
|
||||
Org string `json:"org"`
|
||||
}
|
||||
|
||||
type ConcurrentResults struct {
|
||||
bgpResult string
|
||||
backtraceResult string
|
||||
bgpError error
|
||||
// backtraceError error
|
||||
}
|
||||
|
||||
var IPV4, IPV6 string
|
||||
|
||||
func UpstreamsCheck(language string) {
|
||||
// 添加panic恢复机制
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("\n上游检测出现错误,已跳过")
|
||||
} else {
|
||||
fmt.Println("\nUpstream check failed, skipped")
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "[WARN] Upstream check panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
results := ConcurrentResults{}
|
||||
var wg sync.WaitGroup
|
||||
if IPV4 != "" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] BGP info panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
for i := 0; i < 2; i++ {
|
||||
result, err := bgptools.GetPoPInfo(IPV4)
|
||||
results.bgpError = err
|
||||
if err == nil && result.Result != "" {
|
||||
results.bgpResult = result.Result
|
||||
return
|
||||
}
|
||||
if i == 0 {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] Backtrace panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
result := backtrace.BackTrace(executor.IPV6)
|
||||
results.backtraceResult = result
|
||||
}()
|
||||
wg.Wait()
|
||||
if results.bgpResult != "" {
|
||||
fmt.Print(results.bgpResult)
|
||||
}
|
||||
if results.backtraceResult != "" {
|
||||
fmt.Printf("%s\n", results.backtraceResult)
|
||||
}
|
||||
if language == "zh" {
|
||||
fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考"))
|
||||
fmt.Println(Yellow("同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效"))
|
||||
} else {
|
||||
fmt.Println(Yellow("For accurate routing, check the detailed routes yourself. This result is for reference only."))
|
||||
fmt.Println(Yellow("When multiple routes share the same destination, detection may have passed the aggregation layer; only the first route is reliable."))
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package memorytest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/memorytest/memory"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MemoryTest(language, testMethod string) {
|
||||
var res string
|
||||
if runtime.GOOS == "windows" {
|
||||
if testMethod != "winsat" && testMethod != "" {
|
||||
res = "Detected host is Windows, using Winsat for testing.\n"
|
||||
}
|
||||
res += memory.WinsatTest(language)
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "sysbench":
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" {
|
||||
res = "sysbench test failed, switch to use dd test.\n"
|
||||
res += memory.DDTest(language)
|
||||
}
|
||||
case "dd":
|
||||
res = memory.DDTest(language)
|
||||
default:
|
||||
res = "Unsupported test method"
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
fmt.Printf(res)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package memorytest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
MemoryTest("zh", "sysbench")
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package network1
|
||||
|
||||
import "github.com/oneclickvirt/security/network"
|
||||
|
||||
// 本包在main中不使用
|
||||
func NetworkCheck(checkType string, enableSecurityCheck bool, language string) (string, string, error) {
|
||||
return network.NetworkCheck(checkType, enableSecurityCheck, language)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package network1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIpv4SecurityCheck(t *testing.T) {
|
||||
// 单项测试
|
||||
//result1, _ := Ipv4SecurityCheck("8.8.8.8", nil, "zh")
|
||||
//fmt.Println(result1)
|
||||
//result2, _ := Ipv6SecurityCheck("2001:4860:4860::8844", nil, "zh")
|
||||
//fmt.Println(result2)
|
||||
|
||||
// 全项测试
|
||||
ipInfo, securityInfo, _ := NetworkCheck("both", true, "zh")
|
||||
fmt.Println("--------------------------------------------------")
|
||||
fmt.Printf(ipInfo)
|
||||
fmt.Println("--------------------------------------------------")
|
||||
fmt.Printf(securityInfo)
|
||||
fmt.Println("--------------------------------------------------")
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package ntrace
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/nt3/nt"
|
||||
)
|
||||
|
||||
func TraceRoute3(language, location, checkType string) {
|
||||
nt.TraceRoute(language, location, checkType)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package ntrace
|
||||
|
||||
import "testing"
|
||||
|
||||
// https://github.com/nxtrace/NTrace-core/blob/main/fast_trace/fast_trace.go
|
||||
func TestTraceRoute(t *testing.T) {
|
||||
TraceRoute3("en", "GZ", "ipv4")
|
||||
}
|
||||
13
port/port.go
13
port/port.go
@@ -1,13 +0,0 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/portchecker/email"
|
||||
)
|
||||
|
||||
// 常用端口阻断检测 TCP/UDP/ICMP 协议
|
||||
// 本包不在main中使用
|
||||
func EmailCheck() {
|
||||
res := email.EmailCheck()
|
||||
fmt.Println(res)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
EmailCheck()
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package speedtest
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/speedtest/model"
|
||||
"github.com/oneclickvirt/speedtest/sp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ShowHead(language string) {
|
||||
sp.ShowHead(language)
|
||||
}
|
||||
|
||||
func NearbySP() {
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.NearbySpeedTest()
|
||||
} else {
|
||||
sp.OfficialNearbySpeedTest()
|
||||
}
|
||||
}
|
||||
|
||||
func CustomSP(platform, operator string, num int) {
|
||||
var url, parseType string
|
||||
if strings.ToLower(platform) == "cn" {
|
||||
if strings.ToLower(operator) == "cmcc" {
|
||||
url = model.CnCMCC
|
||||
} else if strings.ToLower(operator) == "cu" {
|
||||
url = model.CnCU
|
||||
} else if strings.ToLower(operator) == "ct" {
|
||||
url = model.CnCT
|
||||
} else if strings.ToLower(operator) == "hk" {
|
||||
url = model.CnHK
|
||||
} else if strings.ToLower(operator) == "tw" {
|
||||
url = model.CnTW
|
||||
} else if strings.ToLower(operator) == "jp" {
|
||||
url = model.CnJP
|
||||
} else if strings.ToLower(operator) == "sg" {
|
||||
url = model.CnSG
|
||||
}
|
||||
parseType = "url"
|
||||
} else if strings.ToLower(platform) == "net" {
|
||||
if strings.ToLower(operator) == "cmcc" {
|
||||
url = model.NetCMCC
|
||||
} else if strings.ToLower(operator) == "cu" {
|
||||
url = model.NetCU
|
||||
} else if strings.ToLower(operator) == "ct" {
|
||||
url = model.NetCT
|
||||
} else if strings.ToLower(operator) == "hk" {
|
||||
url = model.NetHK
|
||||
} else if strings.ToLower(operator) == "tw" {
|
||||
url = model.NetTW
|
||||
} else if strings.ToLower(operator) == "jp" {
|
||||
url = model.NetJP
|
||||
} else if strings.ToLower(operator) == "sg" {
|
||||
url = model.NetSG
|
||||
} else if strings.ToLower(operator) == "global" {
|
||||
url = model.NetGlobal
|
||||
}
|
||||
parseType = "id"
|
||||
}
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.CustomSpeedTest(url, parseType, num)
|
||||
} else {
|
||||
sp.OfficialCustomSpeedTest(url, parseType, num)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package speedtest
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test(t *testing.T) {
|
||||
ShowHead("en")
|
||||
NearbySP()
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package unlocktest
|
||||
|
||||
import (
|
||||
"github.com/oneclickvirt/UnlockTests/utils"
|
||||
"github.com/oneclickvirt/UnlockTests/uts"
|
||||
"github.com/oneclickvirt/defaultset"
|
||||
)
|
||||
|
||||
func MediaTest(language string) string {
|
||||
var res string
|
||||
readStatus := uts.ReadSelect(language, "0")
|
||||
if !readStatus {
|
||||
return ""
|
||||
}
|
||||
if uts.IPV4 {
|
||||
res += defaultset.Blue("IPV4:") + "\n"
|
||||
res += uts.RunTests(utils.Ipv4HttpClient, "ipv4", language, false)
|
||||
return res
|
||||
}
|
||||
if uts.IPV6 {
|
||||
res += defaultset.Blue("IPV6:") + "\n"
|
||||
res += uts.RunTests(utils.Ipv6HttpClient, "ipv6", language, false)
|
||||
return res
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package unlocktest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
fmt.Printf(MediaTest("zh"))
|
||||
}
|
||||
675
utils/utils.go
675
utils/utils.go
@@ -1,19 +1,128 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/oneclickvirt/UnlockTests/uts"
|
||||
"github.com/oneclickvirt/basics/system"
|
||||
"github.com/oneclickvirt/security/network"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/oneclickvirt/UnlockTests/executor"
|
||||
bnetwork "github.com/oneclickvirt/basics/network"
|
||||
"github.com/oneclickvirt/basics/system"
|
||||
butils "github.com/oneclickvirt/basics/utils"
|
||||
. "github.com/oneclickvirt/defaultset"
|
||||
"github.com/oneclickvirt/security/network"
|
||||
)
|
||||
|
||||
// IsAndroid 检测当前是否在 Android (Termux) 环境下运行
|
||||
func IsAndroid() bool {
|
||||
// Termux 会设置 TERMUX_VERSION 或 PREFIX 环境变量
|
||||
if os.Getenv("TERMUX_VERSION") != "" {
|
||||
return true
|
||||
}
|
||||
if prefix := os.Getenv("PREFIX"); strings.Contains(prefix, "termux") {
|
||||
return true
|
||||
}
|
||||
// Android 系统标志文件
|
||||
if _, err := os.Stat("/system/build.prop"); err == nil {
|
||||
return true
|
||||
}
|
||||
// ANDROID_ROOT 环境变量 (Android 系统设置为 /system)
|
||||
if os.Getenv("ANDROID_ROOT") != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// androidDNSServers 是用于修复 Android/Termux 下 DNS 问题的备用服务器列表
|
||||
var androidDNSServers = []string{
|
||||
"nameserver 8.8.8.8",
|
||||
"nameserver 8.8.4.4",
|
||||
"nameserver 1.1.1.1",
|
||||
"nameserver 223.5.5.5",
|
||||
}
|
||||
|
||||
// CheckAndFixAndroidDNS 检测并尝试修复 Android Termux 下的 DNS 解析问题。
|
||||
// 仅在检测到 Android 环境下调用。不出现问题时无任何输出。
|
||||
// language: "zh" 或 "en"
|
||||
func CheckAndFixAndroidDNS(language string) {
|
||||
if !IsAndroid() {
|
||||
return
|
||||
}
|
||||
resolvPath := "/etc/resolv.conf"
|
||||
hasValidDNS := false
|
||||
if data, err := os.ReadFile(resolvPath); err == nil {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver") && len(strings.Fields(line)) >= 2 {
|
||||
hasValidDNS = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasValidDNS {
|
||||
// DNS 配置正常,无需处理
|
||||
return
|
||||
}
|
||||
// /etc/resolv.conf 缺失或无有效 nameserver,尝试自动创建
|
||||
content := strings.Join(androidDNSServers, "\n") + "\n"
|
||||
err := os.WriteFile(resolvPath, []byte(content), 0644)
|
||||
if err != nil {
|
||||
// 写入失败(权限不足),给出双语提示和修复建议
|
||||
fmt.Println("-----------------------------------------------------")
|
||||
fmt.Println("[Android/Termux] 检测到 DNS 解析配置缺失!")
|
||||
fmt.Println("这将导致依赖系统 DNS 的测试项目无法正常运行。")
|
||||
fmt.Println("解决方案(推荐):")
|
||||
fmt.Println(" 通过 Magisk / KernelSU 刷入以下模块后重启手机:")
|
||||
fmt.Println(" https://github.com/weigui404/resolv.conf")
|
||||
fmt.Println("解决方案(临时):")
|
||||
fmt.Println(" 以 root 身份执行: echo 'nameserver 8.8.8.8' > /etc/resolv.conf")
|
||||
fmt.Println("-----------------------------------------------------")
|
||||
fmt.Println("[Android/Termux] DNS resolver config is missing!")
|
||||
fmt.Println("This will cause DNS-dependent tests to fail.")
|
||||
fmt.Println("Fix (recommended):")
|
||||
fmt.Println(" Flash the following module via Magisk / KernelSU and reboot:")
|
||||
fmt.Println(" https://github.com/weigui404/resolv.conf")
|
||||
fmt.Println("Fix (temporary):")
|
||||
fmt.Println(" Run as root: echo 'nameserver 8.8.8.8' > /etc/resolv.conf")
|
||||
fmt.Println("-----------------------------------------------------")
|
||||
} else {
|
||||
// 写入成功
|
||||
if language == "zh" {
|
||||
fmt.Println("[Android/Termux] DNS 配置缺失,已自动写入 /etc/resolv.conf,DNS 解析已恢复。")
|
||||
} else {
|
||||
fmt.Println("[Android/Termux] DNS config was missing; auto-written to /etc/resolv.conf, DNS resolution restored.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取本程序本日及总执行的统计信息
|
||||
type StatsResponse struct {
|
||||
Counter string `json:"counter"`
|
||||
Action string `json:"action"`
|
||||
Total int `json:"total"`
|
||||
Daily int `json:"daily"`
|
||||
Date string `json:"date"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// 获取最新的Github的仓库中的版本
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
// PrintCenteredTitle 根据指定的宽度打印居中标题
|
||||
func PrintCenteredTitle(title string, width int) {
|
||||
// 计算字符串的字符数
|
||||
@@ -27,54 +136,140 @@ func PrintCenteredTitle(title string, width int) {
|
||||
// PrintHead 根据语言打印头部信息
|
||||
func PrintHead(language string, width int, ecsVersion string) {
|
||||
if language == "zh" {
|
||||
PrintCenteredTitle("融合怪测试", width)
|
||||
PrintCenteredTitle("VPS融合怪测试", width)
|
||||
fmt.Printf("版本:%s\n", ecsVersion)
|
||||
fmt.Println("测评频道: https://t.me/vps_reviews\n" +
|
||||
fmt.Println("测评频道: https://t.me/+UHVoo2U4VyA5NTQ1\n" +
|
||||
"Go项目地址:https://github.com/oneclickvirt/ecs\n" +
|
||||
"Shell项目地址:https://github.com/spiritLHLS/ecs")
|
||||
} else {
|
||||
PrintCenteredTitle("Fusion Monster Test", width)
|
||||
PrintCenteredTitle("VPS Fusion Monster Test", width)
|
||||
fmt.Printf("Version: %s\n", ecsVersion)
|
||||
fmt.Println("Review Channel: https://t.me/vps_reviews\n" +
|
||||
"Go Project URL: https://github.com/oneclickvirt/ecs\n" +
|
||||
"Shell Project URL: https://github.com/spiritLHLS/ecs")
|
||||
fmt.Println("Review Channel: https://t.me/+UHVoo2U4VyA5NTQ1\n" +
|
||||
"Go Project: https://github.com/oneclickvirt/ecs\n" +
|
||||
"Shell Project: https://github.com/spiritLHLS/ecs")
|
||||
}
|
||||
}
|
||||
|
||||
// SecurityCheck 执行安全检查
|
||||
func SecurityCheck(language, nt3CheckType string) (string, string, string) {
|
||||
func CheckChina(enableLogger bool, language string) bool {
|
||||
if enableLogger {
|
||||
InitLogger()
|
||||
defer Logger.Sync()
|
||||
}
|
||||
var selectChina bool
|
||||
client := req.C()
|
||||
client.SetTimeout(6 * time.Second)
|
||||
client.R().
|
||||
SetRetryCount(2).
|
||||
SetRetryBackoffInterval(1*time.Second, 3*time.Second).
|
||||
SetRetryFixedInterval(2 * time.Second)
|
||||
ipapiURL := "https://ipapi.co/json"
|
||||
ipapiResp, err := client.R().Get(ipapiURL)
|
||||
if err != nil {
|
||||
if enableLogger {
|
||||
Logger.Info("Failed to get IP info: " + err.Error())
|
||||
}
|
||||
return false
|
||||
}
|
||||
defer ipapiResp.Body.Close()
|
||||
ipapiBody, err := ipapiResp.ToString()
|
||||
if err != nil {
|
||||
if enableLogger {
|
||||
Logger.Info("Failed to read IP info response: " + err.Error())
|
||||
}
|
||||
return false
|
||||
}
|
||||
isInChina := strings.Contains(ipapiBody, "China")
|
||||
if isInChina {
|
||||
var input string
|
||||
if language == "zh" {
|
||||
fmt.Println("根据 ipapi.co 提供的信息,当前IP可能在中国")
|
||||
fmt.Print("是否选用中国专项测试(无平台解锁测试,有三网Ping值测试)? ([y]/n) ")
|
||||
} else {
|
||||
fmt.Println("According to ipapi.co, this IP may be located in China")
|
||||
fmt.Print("Use China-specific test (no platform unlock test, includes 3-network ping test)? ([y]/n) ")
|
||||
}
|
||||
fmt.Scanln(&input)
|
||||
switch strings.ToLower(input) {
|
||||
case "yes", "y":
|
||||
if language == "zh" {
|
||||
fmt.Println("使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Using China-specific test")
|
||||
}
|
||||
selectChina = true
|
||||
case "no", "n":
|
||||
if language == "zh" {
|
||||
fmt.Println("不使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Not using China-specific test")
|
||||
}
|
||||
default:
|
||||
if language == "zh" {
|
||||
fmt.Println("使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Using China-specific test")
|
||||
}
|
||||
selectChina = true
|
||||
}
|
||||
}
|
||||
return selectChina
|
||||
}
|
||||
|
||||
// OnlyBasicsIpInfo 仅检查和输出IP信息
|
||||
func OnlyBasicsIpInfo(language string) (string, string, string) {
|
||||
ipv4, ipv6, ipInfo, _, err := bnetwork.NetworkCheck("both", false, language)
|
||||
if err != nil {
|
||||
return "", "", ""
|
||||
}
|
||||
basicInfo := ipInfo
|
||||
if strings.Contains(ipInfo, "IPV4") && strings.Contains(ipInfo, "IPV6") && ipv4 != "" && ipv6 != "" {
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = true
|
||||
} else if strings.Contains(ipInfo, "IPV4") && ipv4 != "" {
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = false
|
||||
} else if strings.Contains(ipInfo, "IPV6") && ipv6 != "" {
|
||||
executor.IPV6 = true
|
||||
executor.IPV4 = false
|
||||
}
|
||||
basicInfo = strings.ReplaceAll(basicInfo, "\n\n", "\n")
|
||||
return ipv4, ipv6, basicInfo
|
||||
}
|
||||
|
||||
// BasicsAndSecurityCheck 执行安全检查
|
||||
func BasicsAndSecurityCheck(language, nt3CheckType string, securityCheckStatus bool) (string, string, string, string, string) {
|
||||
var wgt sync.WaitGroup
|
||||
var ipInfo, securityInfo, systemInfo string
|
||||
var err error
|
||||
wgt.Add(2)
|
||||
var ipv4, ipv6, ipInfo, securityInfo, systemInfo string
|
||||
wgt.Add(1)
|
||||
go func() {
|
||||
defer wgt.Done()
|
||||
ipInfo, securityInfo, err = network.NetworkCheck("both", true, language)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
ipv4, ipv6, ipInfo, securityInfo, _ = network.NetworkCheck("both", securityCheckStatus, language)
|
||||
// if err != nil {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
}()
|
||||
wgt.Add(1)
|
||||
go func() {
|
||||
defer wgt.Done()
|
||||
systemInfo = system.CheckSystemInfo(language)
|
||||
}()
|
||||
wgt.Wait()
|
||||
basicInfo := systemInfo + ipInfo
|
||||
if strings.Contains(ipInfo, "IPV4") && strings.Contains(ipInfo, "IPV6") {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = true
|
||||
if strings.Contains(ipInfo, "IPV4") && strings.Contains(ipInfo, "IPV6") && ipv4 != "" && ipv6 != "" {
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = true
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv4"
|
||||
}
|
||||
} else if strings.Contains(ipInfo, "IPV4") {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = false
|
||||
} else if strings.Contains(ipInfo, "IPV4") && ipv4 != "" {
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = false
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv4"
|
||||
}
|
||||
} else if strings.Contains(ipInfo, "IPV6") {
|
||||
uts.IPV6 = true
|
||||
uts.IPV4 = false
|
||||
} else if strings.Contains(ipInfo, "IPV6") && ipv6 != "" {
|
||||
executor.IPV6 = true
|
||||
executor.IPV4 = false
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv6"
|
||||
}
|
||||
@@ -85,10 +280,10 @@ func SecurityCheck(language, nt3CheckType string) (string, string, string) {
|
||||
nt3CheckType = "ipv4"
|
||||
}
|
||||
basicInfo = strings.ReplaceAll(basicInfo, "\n\n", "\n")
|
||||
return basicInfo, securityInfo, nt3CheckType
|
||||
return ipv4, ipv6, basicInfo, securityInfo, nt3CheckType
|
||||
}
|
||||
|
||||
// CaptureOutput 捕获函数输出和错误输出并返回字符串
|
||||
// CaptureOutput 捕获函数输出和错误输出,实时输出,并返回字符串
|
||||
func CaptureOutput(f func()) string {
|
||||
// 保存旧的 stdout 和 stderr
|
||||
oldStdout := os.Stdout
|
||||
@@ -107,19 +302,10 @@ func CaptureOutput(f func()) string {
|
||||
// 替换标准输出和标准错误输出为管道写入端
|
||||
os.Stdout = stdoutPipeW
|
||||
os.Stderr = stderrPipeW
|
||||
// 恢复标准输出和标准错误输出
|
||||
defer func() {
|
||||
os.Stdout = oldStdout
|
||||
os.Stderr = oldStderr
|
||||
stdoutPipeW.Close()
|
||||
stderrPipeW.Close()
|
||||
stdoutPipeR.Close()
|
||||
stderrPipeR.Close()
|
||||
}()
|
||||
// 缓冲区
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
// 并发读取 stdout 和 stderr
|
||||
done := make(chan struct{})
|
||||
done := make(chan struct{}, 2)
|
||||
go func() {
|
||||
multiWriter := io.MultiWriter(&stdoutBuf, oldStdout)
|
||||
io.Copy(multiWriter, stdoutPipeR)
|
||||
@@ -132,14 +318,25 @@ func CaptureOutput(f func()) string {
|
||||
}()
|
||||
// 执行函数
|
||||
f()
|
||||
// 确保所有输出都已经刷新
|
||||
os.Stdout.Sync()
|
||||
os.Stderr.Sync()
|
||||
// 等待一小段时间,确保后台goroutine的输出完成
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// 关闭管道写入端,让管道读取端可以读取所有数据
|
||||
stdoutPipeW.Close()
|
||||
stderrPipeW.Close()
|
||||
// 等待两个 goroutine 完成
|
||||
<-done
|
||||
<-done
|
||||
// 恢复标准输出和标准错误输出,并关闭管道读取端
|
||||
os.Stdout = oldStdout
|
||||
os.Stderr = oldStderr
|
||||
stdoutPipeR.Close()
|
||||
stderrPipeR.Close()
|
||||
// 返回捕获的输出字符串
|
||||
return stdoutBuf.String() + stderrBuf.String()
|
||||
// stderrBuf.String()
|
||||
return stdoutBuf.String()
|
||||
}
|
||||
|
||||
// PrintAndCapture 捕获函数输出的同时打印内容
|
||||
@@ -150,26 +347,388 @@ func PrintAndCapture(f func(), tempOutput, output string) string {
|
||||
}
|
||||
|
||||
// UploadText 上传文本内容到指定URL
|
||||
func UploadText(absPath string) (string, error) {
|
||||
url := "https://paste.spiritlhl.net/api/upload"
|
||||
func UploadText(absPath string) (string, string, error) {
|
||||
primaryURL := "http://hpaste.spiritlhl.net/api/UL/upload"
|
||||
backupURL := "https://paste.spiritlhl.net/api/UL/upload"
|
||||
token := network.SecurityUploadToken
|
||||
client := req.DefaultClient()
|
||||
file, _ := os.Open(absPath)
|
||||
resp, err := client.R().
|
||||
SetHeader("Authorization", token).
|
||||
SetHeader("Format", "RANDOM").
|
||||
SetHeader("Max-Views", "0").
|
||||
SetHeader("UploadText", "true").
|
||||
SetHeader("Content-Type", "multipart/form-data").
|
||||
SetHeader("No-JSON", "true").
|
||||
SetFileReader("file", "goecs.txt", file).
|
||||
Post(url)
|
||||
client := req.C().SetTimeout(6 * time.Second)
|
||||
client.R().
|
||||
SetRetryCount(2).
|
||||
SetRetryBackoffInterval(1*time.Second, 5*time.Second).
|
||||
SetRetryFixedInterval(2 * time.Second)
|
||||
// 打开文件
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", fmt.Errorf("failed to open file: %w", err)
|
||||
}
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||
return resp.String(), nil
|
||||
defer file.Close()
|
||||
// 获取文件信息并检查大小
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get file info: %w", err)
|
||||
}
|
||||
if fileInfo.Size() > 25*1024 { // 25KB
|
||||
return "", "", fmt.Errorf("file size exceeds 25KB limit")
|
||||
}
|
||||
// 上传逻辑
|
||||
upload := func(url string) (string, string, error) {
|
||||
file, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to re-open file for %s: %w", url, err)
|
||||
}
|
||||
defer file.Close()
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to read file content for %s: %w", url, err)
|
||||
}
|
||||
resp, err := client.R().
|
||||
SetHeader("Authorization", token).
|
||||
SetFileBytes("file", filepath.Base(absPath), content).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to make request to %s: %w", url, err)
|
||||
}
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 299 && resp.String() != "" {
|
||||
fileID := strings.TrimSpace(resp.String())
|
||||
if strings.Contains(fileID, "show") {
|
||||
fileID = fileID[strings.LastIndex(fileID, "/")+1:]
|
||||
}
|
||||
httpURL := fmt.Sprintf("http://hpaste.spiritlhl.net/#/show/%s", fileID)
|
||||
httpsURL := fmt.Sprintf("https://paste.spiritlhl.net/#/show/%s", fileID)
|
||||
return httpURL, httpsURL, nil
|
||||
}
|
||||
return "", "", fmt.Errorf("upload failed for %s with status code: %d", url, resp.StatusCode)
|
||||
}
|
||||
// 尝试上传到主URL
|
||||
httpURL, httpsURL, err := upload(primaryURL)
|
||||
if err == nil {
|
||||
return httpURL, httpsURL, nil
|
||||
}
|
||||
// 尝试上传到备份URL
|
||||
httpURL, httpsURL, err = upload(backupURL)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to upload to both primary and backup URLs: %w", err)
|
||||
}
|
||||
return httpURL, httpsURL, nil
|
||||
}
|
||||
|
||||
// ProcessAndUpload 创建结果文件并上传文件
|
||||
func ProcessAndUpload(output string, filePath string, enableUplaod bool, language string) (string, string) {
|
||||
// 使用 defer 来处理 panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[ERROR] Fatal error during upload: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(filePath); err == nil {
|
||||
// 文件存在,删除文件
|
||||
err = os.Remove(filePath)
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("无法删除文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to delete file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
// 创建文件
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("无法创建文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to create file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
defer file.Close()
|
||||
// 匹配 ANSI 转义序列
|
||||
ansiRegex := regexp.MustCompile("\x1B\\[[0-9;]+[a-zA-Z]")
|
||||
// 移除 ANSI 转义序列
|
||||
cleanedOutput := ansiRegex.ReplaceAllString(output, "")
|
||||
// 使用 bufio.Writer 提高写入效率
|
||||
writer := bufio.NewWriter(file)
|
||||
_, err = writer.WriteString(cleanedOutput)
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("无法写入文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to write file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
// 确保写入缓冲区的数据都刷新到文件中
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("无法刷新文件缓冲:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to flush file buffer:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
if language == "zh" {
|
||||
fmt.Printf("测试结果已写入 %s\n", filePath)
|
||||
} else {
|
||||
return "", fmt.Errorf("upload failed with status code: %d", resp.StatusCode)
|
||||
fmt.Printf("Test results written to %s\n", filePath)
|
||||
}
|
||||
if enableUplaod {
|
||||
// 获取文件的绝对路径
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("无法获取文件绝对路径:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to get absolute file path:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
// 上传文件并生成短链接
|
||||
http_url, https_url, err := UploadText(absPath)
|
||||
if err != nil {
|
||||
if language == "zh" {
|
||||
fmt.Println("上传失败,无法生成链接")
|
||||
} else {
|
||||
fmt.Println("Upload failed, unable to generate link")
|
||||
}
|
||||
fmt.Println(err.Error())
|
||||
return "", ""
|
||||
}
|
||||
return http_url, https_url
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
var StackType string
|
||||
|
||||
type NetCheckResult struct {
|
||||
HasIPv4 bool
|
||||
HasIPv6 bool
|
||||
Connected bool
|
||||
StackType string // "IPv4", "IPv6", "DualStack", "None"
|
||||
}
|
||||
|
||||
func makeResolver(proto, dnsAddr string) *net.Resolver {
|
||||
return &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
return d.DialContext(ctx, proto, dnsAddr)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 前置联网能力检测
|
||||
func CheckPublicAccess(timeout time.Duration) NetCheckResult {
|
||||
if timeout < 2*time.Second {
|
||||
timeout = 2 * time.Second
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
resultChan := make(chan string, 8)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
checks := []struct {
|
||||
Tag string
|
||||
Addr string
|
||||
Kind string // udp4, udp6, http4, http6
|
||||
}{
|
||||
// UDP DNS
|
||||
{"IPv4", "223.5.5.5:53", "udp4"}, // 阿里 DNS
|
||||
{"IPv4", "8.8.8.8:53", "udp4"}, // Google DNS
|
||||
{"IPv6", "[2400:3200::1]:53", "udp6"}, // 阿里 IPv6 DNS
|
||||
{"IPv6", "[2001:4860:4860::8888]:53", "udp6"}, // Google IPv6 DNS
|
||||
// HTTP HEAD
|
||||
{"IPv4", "https://www.baidu.com", "http4"}, // 百度
|
||||
{"IPv4", "https://1.1.1.1", "http4"}, // Cloudflare
|
||||
{"IPv6", "https://[2400:3200::1]", "http6"}, // 阿里 IPv6
|
||||
{"IPv6", "https://[2606:4700::1111]", "http6"}, // Cloudflare IPv6
|
||||
}
|
||||
for _, check := range checks {
|
||||
wg.Add(1)
|
||||
go func(tag, addr, kind string) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// 记录panic但不影响其他检查,输出到stderr避免污染主输出
|
||||
fmt.Fprintf(os.Stderr, "[WARN] Panic in network check for %s (%s): %v\n", tag, addr, r)
|
||||
}
|
||||
}()
|
||||
switch kind {
|
||||
case "udp4", "udp6":
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout / 4,
|
||||
}
|
||||
conn, err := dialer.DialContext(ctx, kind, addr)
|
||||
if err == nil && conn != nil {
|
||||
conn.Close()
|
||||
select {
|
||||
case resultChan <- tag:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
case "http4", "http6":
|
||||
var resolver *net.Resolver
|
||||
if kind == "http4" {
|
||||
resolver = makeResolver("udp4", "223.5.5.5:53")
|
||||
} else {
|
||||
resolver = makeResolver("udp6", "[2400:3200::1]:53")
|
||||
}
|
||||
dialer := &net.Dialer{
|
||||
Timeout: timeout / 4,
|
||||
Resolver: resolver,
|
||||
}
|
||||
transport := &http.Transport{
|
||||
DialContext: dialer.DialContext,
|
||||
MaxIdleConns: 1,
|
||||
MaxIdleConnsPerHost: 1,
|
||||
IdleConnTimeout: time.Second,
|
||||
TLSHandshakeTimeout: timeout / 4,
|
||||
ResponseHeaderTimeout: timeout / 4,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
client := &http.Client{
|
||||
Timeout: timeout / 4,
|
||||
Transport: transport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "HEAD", addr, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err == nil && resp != nil {
|
||||
if resp.Body != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
if resp.StatusCode < 500 {
|
||||
select {
|
||||
case resultChan <- tag:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}(check.Tag, check.Addr, check.Kind)
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(resultChan)
|
||||
}()
|
||||
hasV4 := false
|
||||
hasV6 := false
|
||||
for {
|
||||
select {
|
||||
case res, ok := <-resultChan:
|
||||
if !ok {
|
||||
goto result
|
||||
}
|
||||
if res == "IPv4" {
|
||||
hasV4 = true
|
||||
}
|
||||
if res == "IPv6" {
|
||||
hasV6 = true
|
||||
}
|
||||
case <-ctx.Done():
|
||||
goto result
|
||||
}
|
||||
}
|
||||
result:
|
||||
stack := "None"
|
||||
if hasV4 && hasV6 {
|
||||
stack = "DualStack"
|
||||
} else if hasV4 {
|
||||
stack = "IPv4"
|
||||
} else if hasV6 {
|
||||
stack = "IPv6"
|
||||
}
|
||||
StackType = stack
|
||||
butils.CheckPublicAccess(3 * time.Second) // 设置basics检测,避免部分测试未启用
|
||||
return NetCheckResult{
|
||||
HasIPv4: hasV4,
|
||||
HasIPv6: hasV6,
|
||||
Connected: hasV4 || hasV6,
|
||||
StackType: stack,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取每日/总的程序执行统计信息
|
||||
func GetGoescStats() (*StatsResponse, error) {
|
||||
client := req.C().SetTimeout(5 * time.Second)
|
||||
var stats StatsResponse
|
||||
resp, err := client.R().
|
||||
SetSuccessResult(&stats).
|
||||
Get("https://hits.spiritlhl.net/goecs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !resp.IsSuccessState() {
|
||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// 统计结果单位转换
|
||||
func FormatGoecsNumber(num int) string {
|
||||
if num >= 1000000 {
|
||||
return fmt.Sprintf("%.1fM", float64(num)/1000000)
|
||||
} else if num >= 1000 {
|
||||
return fmt.Sprintf("%.1fK", float64(num)/1000)
|
||||
}
|
||||
return fmt.Sprintf("%d", num)
|
||||
}
|
||||
|
||||
// 通过Github的API检索仓库最新TAG的版本
|
||||
func GetLatestEcsRelease() (*GitHubRelease, error) {
|
||||
urls := []string{
|
||||
"https://api.github.com/repos/oneclickvirt/ecs/releases/latest",
|
||||
"https://fd.spiritlhl.top/https://api.github.com/repos/oneclickvirt/ecs/releases/latest",
|
||||
"https://githubapi.spiritlhl.top/repos/oneclickvirt/ecs/releases/latest",
|
||||
"https://githubapi.spiritlhl.workers.dev/repos/oneclickvirt/ecs/releases/latest",
|
||||
}
|
||||
client := req.C().SetTimeout(3 * time.Second)
|
||||
for _, url := range urls {
|
||||
var release GitHubRelease
|
||||
resp, err := client.R().
|
||||
SetSuccessResult(&release).
|
||||
Get(url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if resp.IsSuccessState() && release.TagName != "" {
|
||||
return &release, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to fetch release from all sources")
|
||||
}
|
||||
|
||||
// 比较程序版本是否需要升级
|
||||
func CompareVersions(v1, v2 string) int {
|
||||
normalize := func(s string) []int {
|
||||
s = strings.TrimPrefix(strings.ToLower(s), "v")
|
||||
parts := strings.Split(s, ".")
|
||||
result := make([]int, 3)
|
||||
for i := 0; i < 3 && i < len(parts); i++ {
|
||||
n, _ := strconv.Atoi(parts[i])
|
||||
result[i] = n
|
||||
}
|
||||
return result
|
||||
}
|
||||
a := normalize(v1)
|
||||
b := normalize(v2)
|
||||
for i := 0; i < 3; i++ {
|
||||
if a[i] < b[i] {
|
||||
return -1
|
||||
} else if a[i] > b[i] {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
31
utils/utils_test.go
Normal file
31
utils/utils_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// func TestCheckPublicAccess(t *testing.T) {
|
||||
// timeout := 3 * time.Second
|
||||
// result := CheckPublicAccess(timeout)
|
||||
// if result.Connected {
|
||||
// fmt.Print("✅ 本机有公网连接,类型: %s\n", result.StackType)
|
||||
// } else {
|
||||
// fmt.Println("❌ 本机未检测到公网连接")
|
||||
// }
|
||||
// }
|
||||
|
||||
func TestBasicsAndSecurityCheck(t *testing.T) {
|
||||
timeout := 3 * time.Second
|
||||
result := CheckPublicAccess(timeout)
|
||||
if result.Connected {
|
||||
fmt.Printf("✅ 本机有公网连接,类型: %s\n", result.StackType)
|
||||
} else {
|
||||
fmt.Println("❌ 本机未检测到公网连接")
|
||||
}
|
||||
_, _, basicInfo, securityInfo, nt3CheckType := BasicsAndSecurityCheck("zh", "ipv4", false)
|
||||
fmt.Println(basicInfo)
|
||||
fmt.Println(securityInfo)
|
||||
fmt.Println(nt3CheckType)
|
||||
}
|
||||
21
上传所有源码.bat
21
上传所有源码.bat
@@ -1,21 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set repo_path=C:\Users\spiritlhl\Documents\GoWorks\ecs
|
||||
|
||||
git add -A
|
||||
git commit -am"update"
|
||||
|
||||
:push
|
||||
cd %repo_path%
|
||||
|
||||
git push -f origin master
|
||||
if errorlevel 1 (
|
||||
echo Push failed. Retrying in 3 seconds...
|
||||
timeout /nobreak /t 3 >nul
|
||||
goto push
|
||||
)
|
||||
|
||||
echo Push successful.
|
||||
|
||||
endlocal
|
||||
37
发布版本.bat
37
发布版本.bat
@@ -1,37 +0,0 @@
|
||||
@echo off
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
REM 设置仓库路径
|
||||
set repo_path=C:\Users\spiritlhl\Documents\GoWorks\ecs
|
||||
|
||||
REM 进入仓库目录
|
||||
cd %repo_path%
|
||||
|
||||
REM 添加所有更改并提交
|
||||
git add -A
|
||||
git commit -am "update"
|
||||
|
||||
REM 推送代码到 master 分支并创建标签
|
||||
:push
|
||||
git push -f origin master
|
||||
if errorlevel 1 (
|
||||
echo Push failed. Retrying in 3 seconds...
|
||||
timeout /nobreak /t 3 >nul
|
||||
goto push
|
||||
)
|
||||
|
||||
REM 提示用户输入版本号
|
||||
set /p version="Enter the version number (e.g., v1.0.0): "
|
||||
|
||||
REM 创建并推送标签
|
||||
git tag %version%
|
||||
git push origin %version%
|
||||
if errorlevel 1 (
|
||||
echo Tag push failed. Retrying in 3 seconds...
|
||||
timeout /nobreak /t 3 >nul
|
||||
goto push_tag
|
||||
)
|
||||
|
||||
echo Push and tag creation successful.
|
||||
|
||||
endlocal
|
||||
Reference in New Issue
Block a user