mirror of
http://bgp.hk.skcks.cn:10088/github.com/oneclickvirt/ecs
synced 2026-04-21 05:10:32 +08:00
Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b74cbe1e7 | ||
|
|
8137a7ac6a | ||
|
|
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 |
@@ -1,84 +0,0 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -v
|
||||
builds:
|
||||
- id: universal
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- arm
|
||||
- arm64
|
||||
- 386
|
||||
- amd64
|
||||
- mips
|
||||
- mipsle
|
||||
- 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
|
||||
flags:
|
||||
- -trimpath
|
||||
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
|
||||
flags:
|
||||
- -trimpath
|
||||
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
|
||||
@@ -1,522 +0,0 @@
|
||||
version: 2
|
||||
project_name: goecs
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -v
|
||||
|
||||
builds:
|
||||
# Linux AMD64 with CGO
|
||||
- id: linux-amd64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=x86_64-linux-gnu-gcc
|
||||
- CGO_CFLAGS=-O2 -static -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/amd64 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/amd64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux 386 with CGO - 修复了编译器和标志
|
||||
- id: linux-386-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=gcc
|
||||
- CGO_CFLAGS=-m32 -O1 -march=i686 -mtune=generic -fno-stack-protector
|
||||
- CGO_LDFLAGS=-m32
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags="-m32 -static"
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/386 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/386 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux ARM64 with CGO
|
||||
- id: linux-arm64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
- CGO_CFLAGS=-O1 -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/arm64 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/arm64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Windows AMD64 with CGO
|
||||
- id: windows-amd64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=x86_64-w64-mingw32-gcc
|
||||
- CGO_CFLAGS=-O2 -static-libgcc -static-libstdc++
|
||||
- CGO_LDFLAGS=-static-libgcc -static-libstdc++
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for windows/amd64 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built windows/amd64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Windows 386 with CGO - 修复了编译器名称
|
||||
- id: windows-386-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=i686-w64-mingw32-gcc
|
||||
- CGO_CFLAGS=-O2 -static-libgcc -static-libstdc++
|
||||
- CGO_LDFLAGS=-static-libgcc -static-libstdc++
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- windows
|
||||
goarch:
|
||||
- 386
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for windows/386 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built windows/386 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Darwin AMD64 with CGO
|
||||
- id: darwin-amd64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=o64-clang
|
||||
- CGO_CFLAGS=-O2 -arch x86_64 -mmacosx-version-min=10.12
|
||||
- CGO_LDFLAGS=-arch x86_64 -mmacosx-version-min=10.12
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for darwin/amd64 (CGO)"
|
||||
- echo "Checking osxcross tools..."
|
||||
- which o64-clang || echo "o64-clang not found"
|
||||
- which o64-clang++ || echo "o64-clang++ not found"
|
||||
post:
|
||||
- echo "Successfully built darwin/amd64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Darwin ARM64 with CGO
|
||||
- id: darwin-arm64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=oa64-clang
|
||||
- CGO_CFLAGS=-O2 -arch arm64 -mmacosx-version-min=11.0
|
||||
- CGO_LDFLAGS=-arch arm64 -mmacosx-version-min=11.0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- darwin
|
||||
goarch:
|
||||
- arm64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for darwin/arm64 (CGO)"
|
||||
- echo "Checking osxcross tools..."
|
||||
- which oa64-clang || echo "oa64-clang not found"
|
||||
- which oa64-clang++ || echo "oa64-clang++ not found"
|
||||
post:
|
||||
- echo "Successfully built darwin/arm64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux RISC-V 64 with CGO
|
||||
- id: linux-riscv64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=riscv64-linux-gnu-gcc
|
||||
- CGO_CFLAGS=-O1 -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- riscv64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/riscv64 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/riscv64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux MIPS64 with CGO
|
||||
- id: linux-mips64-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=mips64-linux-gnuabi64-gcc
|
||||
- CGO_CFLAGS=-O1 -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- mips64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/mips64 (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/mips64 (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux MIPS64LE with CGO
|
||||
- id: linux-mips64le-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=mips64el-linux-gnuabi64-gcc
|
||||
- CGO_CFLAGS=-O1 -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- mips64le
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/mips64le (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/mips64le (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux PPC64LE with CGO
|
||||
- id: linux-ppc64le-cgo
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- CC=powerpc64le-linux-gnu-gcc
|
||||
- CGO_CFLAGS=-O1 -fno-stack-protector
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0 -extldflags=-static
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- ppc64le
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/ppc64le (CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/ppc64le (CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux ARM (no CGO)
|
||||
- id: linux-arm-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm
|
||||
goarm:
|
||||
- "5"
|
||||
- "6"
|
||||
- "7"
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/arm (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/arm (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux S390X (no CGO)
|
||||
- id: linux-s390x-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/s390x (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/s390x (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux MIPS (no CGO)
|
||||
- id: linux-mips-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- mips
|
||||
gomips:
|
||||
- softfloat
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/mips (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/mips (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux MIPSLE (no CGO)
|
||||
- id: linux-mipsle-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- mipsle
|
||||
gomips:
|
||||
- softfloat
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/mipsle (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/mipsle (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# Linux PPC64 (no CGO)
|
||||
- id: linux-ppc64-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- ppc64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for linux/ppc64 (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built linux/ppc64 (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# FreeBSD AMD64 (no CGO)
|
||||
- id: freebsd-amd64-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for freebsd/amd64 (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built freebsd/amd64 (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
# FreeBSD ARM64 (no CGO)
|
||||
- id: freebsd-arm64-nocgo
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.arch={{.Arch}} -checklinkname=0
|
||||
flags:
|
||||
- -trimpath
|
||||
goos:
|
||||
- freebsd
|
||||
goarch:
|
||||
- arm64
|
||||
main: ./
|
||||
binary: goecs
|
||||
no_unique_dist_dir: true
|
||||
hooks:
|
||||
pre:
|
||||
- echo "Starting build for freebsd/arm64 (no CGO)"
|
||||
post:
|
||||
- echo "Successfully built freebsd/arm64 (no CGO)"
|
||||
- echo "---"
|
||||
|
||||
universal_binaries:
|
||||
- name_template: "goecs"
|
||||
replace: false
|
||||
ids:
|
||||
- darwin-amd64-cgo
|
||||
- darwin-arm64-cgo
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
algorithm: sha256
|
||||
disable: false
|
||||
ids:
|
||||
- linux-amd64-cgo
|
||||
- linux-386-cgo
|
||||
- linux-arm64-cgo
|
||||
- linux-riscv64-cgo
|
||||
- linux-mips64-cgo
|
||||
- linux-mips64le-cgo
|
||||
- linux-ppc64le-cgo
|
||||
- windows-amd64-cgo
|
||||
- windows-386-cgo
|
||||
- darwin-amd64-cgo
|
||||
- darwin-arm64-cgo
|
||||
- linux-arm-nocgo
|
||||
- linux-s390x-nocgo
|
||||
- linux-mips-nocgo
|
||||
- linux-mipsle-nocgo
|
||||
- linux-ppc64-nocgo
|
||||
- freebsd-amd64-nocgo
|
||||
- freebsd-arm64-nocgo
|
||||
extra_files:
|
||||
- glob: "./goecs.sh"
|
||||
|
||||
snapshot:
|
||||
name_template: "goecs"
|
||||
|
||||
archives:
|
||||
- id: default
|
||||
name_template: "goecs_{{ .Os }}_{{ .Arch }}"
|
||||
format: zip
|
||||
files:
|
||||
- none*
|
||||
allow_different_binary_count: true
|
||||
builds:
|
||||
- linux-amd64-cgo
|
||||
- linux-386-cgo
|
||||
- linux-arm64-cgo
|
||||
- linux-riscv64-cgo
|
||||
- linux-mips64-cgo
|
||||
- linux-mips64le-cgo
|
||||
- linux-ppc64le-cgo
|
||||
- windows-amd64-cgo
|
||||
- windows-386-cgo
|
||||
- darwin-amd64-cgo
|
||||
- darwin-arm64-cgo
|
||||
- linux-arm-nocgo
|
||||
- linux-s390x-nocgo
|
||||
- linux-mips-nocgo
|
||||
- linux-mipsle-nocgo
|
||||
- linux-ppc64-nocgo
|
||||
- freebsd-amd64-nocgo
|
||||
- freebsd-arm64-nocgo
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^chore"
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
- go mod tidy
|
||||
- New translations
|
||||
@@ -1,14 +0,0 @@
|
||||
// clearScreen 清屏
|
||||
func clearScreen() {
|
||||
var cmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
cmd = exec.Command("cmd", "/c", "cls")
|
||||
case "darwin":
|
||||
cmd = exec.Command("clear")
|
||||
default:
|
||||
cmd = exec.Command("clear")
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
_ = cmd.Run()
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package basic1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_basic(t *testing.T) {
|
||||
Basic("zh")
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package basic1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/basics/network"
|
||||
"github.com/oneclickvirt/basics/system"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 本包不在main中使用,仅做测试使用,真正调用的在 utils 中的 BasicsAndSecurityCheck
|
||||
func Basic(language string) {
|
||||
ipInfo, _, _ := network.NetworkCheck("both", false, language)
|
||||
systemInfo := system.CheckSystemInfo(language)
|
||||
basicInfo := strings.ReplaceAll(systemInfo+ipInfo, "\n\n", "\n")
|
||||
fmt.Print(basicInfo)
|
||||
}
|
||||
132
.back/build.yaml
132
.back/build.yaml
@@ -1,132 +0,0 @@
|
||||
name: Goreleaser
|
||||
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:
|
||||
- name: Configure git safe directory
|
||||
run: |
|
||||
git config --global --add safe.directory /__w/ecs/ecs
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.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: Install missing cross-compilation tools
|
||||
run: |
|
||||
echo "Installing missing cross-compilation tools..."
|
||||
apt-get update
|
||||
PACKAGES=(
|
||||
gcc-multilib
|
||||
g++-multilib
|
||||
linux-libc-dev
|
||||
linux-libc-dev:i386
|
||||
libc6-dev-i386
|
||||
libc6-dev-i386-cross
|
||||
gcc-aarch64-linux-gnu
|
||||
gcc-riscv64-linux-gnu
|
||||
gcc-mips64-linux-gnuabi64
|
||||
gcc-mips64el-linux-gnuabi64
|
||||
gcc-powerpc64le-linux-gnu
|
||||
gcc-mingw-w64-x86-64
|
||||
gcc-mingw-w64-i686
|
||||
libc6-dev-amd64-cross
|
||||
libc6-dev-arm64-cross
|
||||
libc6-dev-riscv64-cross
|
||||
libc6-dev-mips64-cross
|
||||
libc6-dev-mips64el-cross
|
||||
libc6-dev-ppc64el-cross
|
||||
)
|
||||
for pkg in "${PACKAGES[@]}"; do
|
||||
echo "Installing $pkg..."
|
||||
apt-get install -y "$pkg" || echo "Failed to install $pkg, continuing..."
|
||||
done
|
||||
|
||||
- name: Verify cross-compilation tools
|
||||
run: |
|
||||
echo "Checking available cross-compilation tools..."
|
||||
echo "=== GCC compilers ==="
|
||||
which gcc || echo "gcc not found"
|
||||
which x86_64-linux-gnu-gcc || echo "x86_64-linux-gnu-gcc not found"
|
||||
which aarch64-linux-gnu-gcc || echo "aarch64-linux-gnu-gcc not found"
|
||||
which riscv64-linux-gnu-gcc || echo "riscv64-linux-gnu-gcc not found"
|
||||
which mips64-linux-gnuabi64-gcc || echo "mips64-linux-gnuabi64-gcc not found"
|
||||
which mips64el-linux-gnuabi64-gcc || echo "mips64el-linux-gnuabi64-gcc not found"
|
||||
which powerpc64le-linux-gnu-gcc || echo "powerpc64le-linux-gnu-gcc not found"
|
||||
echo "=== MinGW compilers ==="
|
||||
which x86_64-w64-mingw32-gcc || echo "x86_64-w64-mingw32-gcc not found"
|
||||
which i686-w64-mingw32-gcc || echo "i686-w64-mingw32-gcc not found"
|
||||
echo "=== OSXCross compilers ==="
|
||||
which o64-clang || echo "o64-clang not found"
|
||||
which oa64-clang || echo "oa64-clang not found"
|
||||
which o64-clang++ || echo "o64-clang++ not found"
|
||||
which oa64-clang++ || echo "oa64-clang++ not found"
|
||||
echo "=== Clang compilers ==="
|
||||
which clang || echo "clang not found"
|
||||
echo "=== Available gcc binaries ==="
|
||||
ls -la /usr/bin/*gcc* | head -20
|
||||
echo "=== Available clang binaries ==="
|
||||
ls -la /usr/bin/*clang* | head -10
|
||||
echo "=== OSXCross directory ==="
|
||||
ls -la /usr/osxcross/bin/ 2>/dev/null || echo "OSXCross not found in /usr/osxcross/bin/"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --parallelism 1 --verbose
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
GOPRIVATE: github.com/oneclickvirt/security
|
||||
|
||||
- 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 }}
|
||||
@@ -1,467 +0,0 @@
|
||||
name: Build and Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Release Check And Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"* %H %s" "$TAG" | head -20)
|
||||
else
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"* %H %s" "$PREV_TAG..$TAG")
|
||||
fi
|
||||
FULL_CHANGELOG="## Changelog"$'\n'"$CHANGELOG"
|
||||
echo "$FULL_CHANGELOG" > changelog.txt
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$FULL_CHANGELOG" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create or update release
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
CHANGELOG_BODY=$(cat changelog.txt | jq -Rs .)
|
||||
RELEASE_EXISTS=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id // empty')
|
||||
if [ -z "$RELEASE_EXISTS" ]; then
|
||||
curl -s -X POST -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$CHANGELOG_BODY,\"draft\":false,\"prerelease\":false}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases"
|
||||
else
|
||||
curl -s -X PATCH -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\":$CHANGELOG_BODY}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_EXISTS"
|
||||
fi
|
||||
|
||||
- name: Delete existing release assets
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
if [ "$RELEASE_ID" != "null" ]; then
|
||||
ASSETS=$(curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets" | jq -r '.[] | .id')
|
||||
for asset in $ASSETS; do
|
||||
curl -s -X DELETE -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset"
|
||||
done
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
release-binary:
|
||||
name: Release Go Binary
|
||||
needs: build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
cgo_enabled: "1"
|
||||
cc: x86_64-linux-gnu-gcc
|
||||
cflags: "-O2 -static -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: 386
|
||||
cgo_enabled: "1"
|
||||
cc: x86_64-linux-gnu-gcc
|
||||
cflags: "-m32 -O1 -march=i686 -mtune=generic -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-multilib"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
cgo_enabled: "1"
|
||||
cc: aarch64-linux-gnu-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-aarch64-linux-gnu"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
cgo_enabled: "1"
|
||||
cc: riscv64-linux-gnu-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-riscv64-linux-gnu"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips64
|
||||
cgo_enabled: "1"
|
||||
cc: mips64-linux-gnuabi64-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-mips64-linux-gnuabi64"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips64le
|
||||
cgo_enabled: "1"
|
||||
cc: mips64el-linux-gnuabi64-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-mips64el-linux-gnuabi64"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
cgo_enabled: "1"
|
||||
cc: powerpc64le-linux-gnu-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-powerpc64le-linux-gnu"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
# goarm: 7
|
||||
cgo_enabled: "1"
|
||||
cc: arm-linux-gnueabihf-gcc
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-arm-linux-gnueabihf"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
cgo_enabled: "1"
|
||||
cc: x86_64-w64-mingw32-gcc
|
||||
cflags: "-O2 -static-libgcc -static-libstdc++"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-mingw-w64-x86-64"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
cgo_enabled: "1"
|
||||
cc: i686-w64-mingw32-gcc
|
||||
cflags: "-O2 -static-libgcc -static-libstdc++"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential gcc-mingw-w64-i686"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-extldflags=-static -s -w"
|
||||
packages: "build-essential"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: macos-latest
|
||||
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: macos-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: s390x
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mipsle
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: freebsd
|
||||
goarch: amd64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24.5
|
||||
|
||||
- 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: Install cross-compilation tools
|
||||
if: matrix.runner != 'macos-latest'
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
case "${{ matrix.goos }}-${{ matrix.goarch }}" in
|
||||
linux-386)
|
||||
sudo apt-get install -y build-essential gcc-multilib g++-multilib ;;
|
||||
linux-arm64)
|
||||
sudo apt-get install -y build-essential gcc-aarch64-linux-gnu ;;
|
||||
linux-riscv64)
|
||||
sudo apt-get install -y build-essential gcc-riscv64-linux-gnu ;;
|
||||
linux-mips64)
|
||||
sudo apt-get install -y build-essential gcc-mips64-linux-gnuabi64 ;;
|
||||
linux-mips64le)
|
||||
sudo apt-get install -y build-essential gcc-mips64el-linux-gnuabi64 ;;
|
||||
linux-ppc64le)
|
||||
sudo apt-get install -y build-essential gcc-powerpc64le-linux-gnu ;;
|
||||
linux-arm)
|
||||
sudo apt-get install -y build-essential gcc-arm-linux-gnueabihf ;;
|
||||
windows-amd64|windows-386)
|
||||
sudo apt-get install -y build-essential gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 ;;
|
||||
*)
|
||||
sudo apt-get install -y build-essential ;;
|
||||
esac
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Binary
|
||||
env:
|
||||
CGO_ENABLED: ${{ matrix.cgo_enabled }}
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CC: ${{ matrix.cc }}
|
||||
CGO_CFLAGS: ${{ matrix.cflags }}
|
||||
CGO_LDFLAGS: ${{ matrix.ldflags }}
|
||||
run: |
|
||||
go clean -cache -modcache -testcache
|
||||
|
||||
# 设置额外的环境变量
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
export GOARM=${{ matrix.goarm }}
|
||||
fi
|
||||
if [[ -n "${{ matrix.gomips }}" ]]; then
|
||||
export GOMIPS=${{ matrix.gomips }}
|
||||
fi
|
||||
|
||||
# 针对 Darwin 的特殊处理
|
||||
if [[ "${{ matrix.cgo_enabled }}" == "1" && "${{ matrix.goos }}" == "darwin" ]]; then
|
||||
if [[ "${{ matrix.goarch }}" == "amd64" ]]; then
|
||||
export CC="x86_64-apple-darwin21.4-clang"
|
||||
export CXX="x86_64-apple-darwin21.4-clang++"
|
||||
elif [[ "${{ matrix.goarch }}" == "arm64" ]]; then
|
||||
export CC="aarch64-apple-darwin21.4-clang"
|
||||
export CXX="aarch64-apple-darwin21.4-clang++"
|
||||
fi
|
||||
export OSXCROSS_ROOT="${OSXCROSS_ROOT}"
|
||||
elif [[ "${{ matrix.cgo_enabled }}" == "1" && "${{ matrix.runner }}" != "macos-latest" ]]; then
|
||||
# 对于 Windows 的特殊处理
|
||||
if [[ "${{ matrix.goos }}" == "windows" ]]; then
|
||||
export CGO_LDFLAGS="-static-libgcc -static-libstdc++"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 测试编译器(仅在启用 CGO 时)
|
||||
if [[ "${{ matrix.cgo_enabled }}" == "1" && -n "$CC" ]]; then
|
||||
echo 'int main() { return 0; }' > test.c
|
||||
$CC $CGO_CFLAGS test.c -o test || exit 1
|
||||
rm -f test.c test
|
||||
fi
|
||||
|
||||
# 清理和准备
|
||||
rm -rf vendor/
|
||||
go mod download
|
||||
go mod tidy
|
||||
mkdir -p bin
|
||||
|
||||
# 设置二进制文件名
|
||||
BINARY_NAME="goecs"
|
||||
if [[ "${{ matrix.goos }}" == "windows" ]]; then
|
||||
BINARY_NAME="${BINARY_NAME}.exe"
|
||||
fi
|
||||
|
||||
# 构建 LDFLAGS
|
||||
LDFLAGS="-s -w -X main.version=${{ steps.tag.outputs.version }} -X main.arch=${{ matrix.goarch }}"
|
||||
if [[ "${{ matrix.cgo_enabled }}" == "1" ]]; then
|
||||
LDFLAGS="${LDFLAGS} -checklinkname=0 ${{ matrix.ldflags }}"
|
||||
else
|
||||
LDFLAGS="${LDFLAGS} -checklinkname=0 ${{ matrix.ldflags }}"
|
||||
fi
|
||||
|
||||
# 执行构建
|
||||
echo "Building for GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=$CGO_ENABLED"
|
||||
go build -a -o bin/$BINARY_NAME -ldflags="$LDFLAGS" -trimpath ./
|
||||
|
||||
# 验证文件是否存在
|
||||
[[ -f "bin/$BINARY_NAME" ]] || exit 1
|
||||
|
||||
# 显示构建信息
|
||||
echo "Built binary: bin/$BINARY_NAME"
|
||||
ls -la bin/
|
||||
if command -v file >/dev/null 2>&1; then
|
||||
file bin/$BINARY_NAME
|
||||
fi
|
||||
|
||||
- name: Create ZIP archive
|
||||
run: |
|
||||
cd bin
|
||||
BINARY_NAME="goecs"
|
||||
if [[ "${{ matrix.goos }}" == "windows" ]]; then
|
||||
BINARY_NAME="${BINARY_NAME}.exe"
|
||||
fi
|
||||
ZIP_NAME="goecs_${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
ZIP_NAME="${ZIP_NAME}v${{ matrix.goarm }}"
|
||||
fi
|
||||
if [[ -n "${{ matrix.gomips }}" ]]; then
|
||||
ZIP_NAME="${ZIP_NAME}_${{ matrix.gomips }}"
|
||||
fi
|
||||
ZIP_NAME="${ZIP_NAME}.zip"
|
||||
zip "$ZIP_NAME" "$BINARY_NAME"
|
||||
|
||||
- name: Upload to Release
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
cd bin
|
||||
for file in *.zip; do
|
||||
if [[ -f "$file" ]]; then
|
||||
curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/zip" \
|
||||
--data-binary @"$file" \
|
||||
"https://uploads.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=$file"
|
||||
fi
|
||||
done
|
||||
|
||||
checksums:
|
||||
name: Generate Checksums
|
||||
runs-on: ubuntu-latest
|
||||
needs: release-binary
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
mkdir -p assets
|
||||
ASSETS=$(curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets")
|
||||
echo "$ASSETS" | jq -r '.[] | select(.name | endswith(".zip")) | .browser_download_url' | while read url; do
|
||||
filename=$(basename "$url")
|
||||
curl -L -H "Authorization: Bearer ${{ secrets.GHT }}" "$url" -o "assets/$filename"
|
||||
done
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd assets
|
||||
sha256sum *.zip > checksums.txt
|
||||
if [[ -f "../goecs.sh" ]]; then
|
||||
sha256sum ../goecs.sh >> checksums.txt
|
||||
fi
|
||||
|
||||
- name: Upload checksums
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
--data-binary @assets/checksums.txt \
|
||||
"https://uploads.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=checksums.txt"
|
||||
|
||||
update-script:
|
||||
name: Update Script Version
|
||||
runs-on: ubuntu-latest
|
||||
needs: checksums
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update goecs.sh version
|
||||
run: |
|
||||
VERSION="${{ steps.tag.outputs.version }}"
|
||||
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 "goecs.sh" ]; then
|
||||
sed -i "s/\(_yellow \"Unable to get version info, using default version \).*\(\".*\)/\1$VERSION\2/" "goecs.sh"
|
||||
sed -i "s/\(ECS_VERSION=\"\).*\(\"\)/\1$VERSION\2/" "goecs.sh"
|
||||
if ! git diff --quiet "goecs.sh"; then
|
||||
git add "goecs.sh"
|
||||
git commit -m "chore: update ECS_VERSION to $VERSION in goecs.sh"
|
||||
git push origin $BRANCH
|
||||
fi
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
@@ -1,527 +0,0 @@
|
||||
name: Build and Release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Release Check And Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 "$TAG^" 2>/dev/null || echo "")
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"* %H %s" "$TAG" | head -20)
|
||||
else
|
||||
CHANGELOG=$(git log --oneline --pretty=format:"* %H %s" "$PREV_TAG..$TAG")
|
||||
fi
|
||||
FULL_CHANGELOG="## Changelog"$'\n'"$CHANGELOG"
|
||||
echo "$FULL_CHANGELOG" > changelog.txt
|
||||
echo "changelog<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$FULL_CHANGELOG" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create or update release
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
CHANGELOG_BODY=$(cat changelog.txt | jq -Rs .)
|
||||
RELEASE_EXISTS=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id // empty')
|
||||
if [ -z "$RELEASE_EXISTS" ]; then
|
||||
curl -s -X POST -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"body\":$CHANGELOG_BODY,\"draft\":false,\"prerelease\":false}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases"
|
||||
else
|
||||
curl -s -X PATCH -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\":$CHANGELOG_BODY}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_EXISTS"
|
||||
fi
|
||||
|
||||
- name: Delete existing release assets
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
if [ "$RELEASE_ID" != "null" ]; then
|
||||
ASSETS=$(curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets" | jq -r '.[] | .id')
|
||||
for asset in $ASSETS; do
|
||||
curl -s -X DELETE -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset"
|
||||
done
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
build-musl-toolchain:
|
||||
name: Build musl Cross-Compiler Toolchain
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-linux-musl
|
||||
- i686-linux-musl
|
||||
- aarch64-linux-musl
|
||||
- riscv64-linux-musl
|
||||
- mips64-linux-musl
|
||||
- mips64el-linux-musl
|
||||
- powerpc64le-linux-musl
|
||||
- arm-linux-musleabihf
|
||||
steps:
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y build-essential curl
|
||||
|
||||
- name: Cache musl toolchain
|
||||
id: cache-musl
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /opt/musl-${{ matrix.target }}
|
||||
key: musl-toolchain-${{ matrix.target }}-v2
|
||||
|
||||
- name: Build musl cross-compiler
|
||||
if: steps.cache-musl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# Clone musl-cross-make
|
||||
git clone https://github.com/richfelker/musl-cross-make.git
|
||||
cd musl-cross-make
|
||||
|
||||
# Create config for target
|
||||
cat > config.mak << EOF
|
||||
TARGET = ${{ matrix.target }}
|
||||
OUTPUT = /opt/musl-${{ matrix.target }}
|
||||
COMMON_CONFIG += --disable-nls
|
||||
GCC_CONFIG += --enable-languages=c,c++
|
||||
GCC_CONFIG += --disable-libquadmath --disable-decimal-float
|
||||
GCC_CONFIG += --disable-libitm --disable-fixed-point
|
||||
EOF
|
||||
|
||||
# Build the toolchain
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
|
||||
# Verify installation
|
||||
ls -la /opt/musl-${{ matrix.target }}/bin/
|
||||
/opt/musl-${{ matrix.target }}/bin/${{ matrix.target }}-gcc --version
|
||||
|
||||
- name: Create toolchain artifact
|
||||
run: |
|
||||
sudo tar -czf musl-${{ matrix.target }}-toolchain.tar.gz -C /opt musl-${{ matrix.target }}
|
||||
|
||||
- name: Upload toolchain artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: musl-${{ matrix.target }}-toolchain
|
||||
path: musl-${{ matrix.target }}-toolchain.tar.gz
|
||||
retention-days: 1
|
||||
|
||||
release-binary:
|
||||
name: Release Go Binary
|
||||
needs: [build, build-musl-toolchain]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
cgo_enabled: "1"
|
||||
musl_target: x86_64-linux-musl
|
||||
cflags: "-O2 -static -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: 386
|
||||
cgo_enabled: "1"
|
||||
musl_target: i686-linux-musl
|
||||
cflags: "-O1 -march=i686 -mtune=generic -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
cgo_enabled: "1"
|
||||
musl_target: aarch64-linux-musl
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: riscv64
|
||||
cgo_enabled: "1"
|
||||
musl_target: riscv64-linux-musl
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips64
|
||||
cgo_enabled: "1"
|
||||
musl_target: mips64-linux-musl
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips64le
|
||||
cgo_enabled: "1"
|
||||
musl_target: mips64el-linux-musl
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: ppc64le
|
||||
cgo_enabled: "1"
|
||||
musl_target: powerpc64le-linux-musl
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: arm
|
||||
cgo_enabled: "1"
|
||||
musl_target: arm-linux-musleabihf
|
||||
cflags: "-O1 -fno-stack-protector"
|
||||
ldflags: "-extldflags=-latomic -static"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
cgo_enabled: "1"
|
||||
cc: x86_64-w64-mingw32-gcc
|
||||
cflags: "-O2 -static-libgcc -static-libstdc++"
|
||||
ldflags: "-extldflags=-static"
|
||||
packages: "build-essential gcc-mingw-w64-x86-64"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: 386
|
||||
cgo_enabled: "1"
|
||||
cc: i686-w64-mingw32-gcc
|
||||
cflags: "-O2 -static-libgcc -static-libstdc++"
|
||||
ldflags: "-extldflags=-static"
|
||||
packages: "build-essential gcc-mingw-w64-i686"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
packages: "build-essential"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: macos-latest
|
||||
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: macos-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: s390x
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mips
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: mipsle
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: linux
|
||||
goarch: ppc64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: freebsd
|
||||
goarch: amd64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
- goos: freebsd
|
||||
goarch: arm64
|
||||
cgo_enabled: "0"
|
||||
ldflags: "-s -w"
|
||||
runner: ubuntu-latest
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.24.5
|
||||
|
||||
- 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: Download musl toolchain
|
||||
if: matrix.musl_target != ''
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: musl-${{ matrix.musl_target }}-toolchain
|
||||
|
||||
- name: Setup musl toolchain
|
||||
if: matrix.musl_target != ''
|
||||
run: |
|
||||
sudo tar -xzf musl-${{ matrix.musl_target }}-toolchain.tar.gz -C /opt/
|
||||
echo "/opt/musl-${{ matrix.musl_target }}/bin" >> $GITHUB_PATH
|
||||
|
||||
# Verify toolchain is working
|
||||
/opt/musl-${{ matrix.musl_target }}/bin/${{ matrix.musl_target }}-gcc --version
|
||||
|
||||
# Test compiler
|
||||
echo 'int main() { return 0; }' > test.c
|
||||
/opt/musl-${{ matrix.musl_target }}/bin/${{ matrix.musl_target }}-gcc ${{ matrix.cflags }} test.c -o test
|
||||
rm -f test.c test
|
||||
|
||||
- name: Install cross-compilation tools (non-musl)
|
||||
if: matrix.runner != 'macos-latest' && matrix.musl_target == ''
|
||||
run: |
|
||||
sudo systemctl restart systemd-resolved || true
|
||||
sudo apt-get update -qq || (sleep 10 && sudo apt-get update -qq)
|
||||
|
||||
case "${{ matrix.goos }}-${{ matrix.goarch }}" in
|
||||
windows-amd64|windows-386)
|
||||
for i in 1 2 3; do
|
||||
sudo apt-get install -y build-essential gcc-mingw-w64-x86-64 gcc-mingw-w64-i686 && break || sleep 10
|
||||
done ;;
|
||||
*)
|
||||
sudo systemctl restart systemd-resolved || true
|
||||
for i in 1 2 3; do
|
||||
sudo apt-get install -y build-essential && break || sleep 10
|
||||
done ;;
|
||||
esac
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build Binary
|
||||
env:
|
||||
CGO_ENABLED: ${{ matrix.cgo_enabled }}
|
||||
GOOS: ${{ matrix.goos }}
|
||||
GOARCH: ${{ matrix.goarch }}
|
||||
CGO_CFLAGS: ${{ matrix.cflags }}
|
||||
CGO_LDFLAGS: ${{ matrix.ldflags }}
|
||||
run: |
|
||||
go clean -cache -modcache -testcache
|
||||
|
||||
# Set CC based on target
|
||||
if [[ "${{ matrix.musl_target }}" != "" ]]; then
|
||||
export CC="/opt/musl-${{ matrix.musl_target }}/bin/${{ matrix.musl_target }}-gcc"
|
||||
export CXX="/opt/musl-${{ matrix.musl_target }}/bin/${{ matrix.musl_target }}-g++"
|
||||
elif [[ "${{ matrix.cc }}" != "" ]]; then
|
||||
export CC="${{ matrix.cc }}"
|
||||
fi
|
||||
|
||||
# 设置额外的环境变量
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
export GOARM=${{ matrix.goarm }}
|
||||
fi
|
||||
if [[ -n "${{ matrix.gomips }}" ]]; then
|
||||
export GOMIPS=${{ matrix.gomips }}
|
||||
fi
|
||||
|
||||
# 针对 Darwin 的特殊处理
|
||||
if [[ "${{ matrix.cgo_enabled }}" == "1" && "${{ matrix.goos }}" == "darwin" ]]; then
|
||||
if [[ "${{ matrix.goarch }}" == "amd64" ]]; then
|
||||
export CC="x86_64-apple-darwin21.4-clang"
|
||||
export CXX="x86_64-apple-darwin21.4-clang++"
|
||||
elif [[ "${{ matrix.goarch }}" == "arm64" ]]; then
|
||||
export CC="aarch64-apple-darwin21.4-clang"
|
||||
export CXX="aarch64-apple-darwin21.4-clang++"
|
||||
fi
|
||||
export OSXCROSS_ROOT="${OSXCROSS_ROOT}"
|
||||
fi
|
||||
|
||||
# 清理和准备
|
||||
rm -rf vendor/
|
||||
go mod download
|
||||
go mod tidy
|
||||
mkdir -p bin
|
||||
|
||||
# 设置二进制文件名
|
||||
BINARY_NAME="goecs"
|
||||
if [[ "${{ matrix.goos }}" == "windows" ]]; then
|
||||
BINARY_NAME="${BINARY_NAME}.exe"
|
||||
fi
|
||||
|
||||
# 构建 LDFLAGS
|
||||
LDFLAGS="-s -w -X main.version=${{ steps.tag.outputs.version }} -X main.arch=${{ matrix.goarch }}"
|
||||
if [[ "${{ matrix.cgo_enabled }}" == "1" ]]; then
|
||||
LDFLAGS="${LDFLAGS} -checklinkname=0 ${{ matrix.ldflags }}"
|
||||
else
|
||||
LDFLAGS="${LDFLAGS} -checklinkname=0 ${{ matrix.ldflags }}"
|
||||
fi
|
||||
|
||||
# 执行构建
|
||||
echo "Building for GOOS=$GOOS GOARCH=$GOARCH CGO_ENABLED=$CGO_ENABLED"
|
||||
if [[ -n "$CC" ]]; then
|
||||
echo "Using CC=$CC"
|
||||
fi
|
||||
|
||||
go build -a -o bin/$BINARY_NAME -ldflags="$LDFLAGS" -trimpath ./
|
||||
|
||||
# 验证文件是否存在
|
||||
[[ -f "bin/$BINARY_NAME" ]] || exit 1
|
||||
|
||||
# 显示构建信息
|
||||
echo "Built binary: bin/$BINARY_NAME"
|
||||
ls -la bin/
|
||||
if command -v file >/dev/null 2>&1; then
|
||||
file bin/$BINARY_NAME
|
||||
fi
|
||||
|
||||
- name: Create ZIP archive
|
||||
run: |
|
||||
cd bin
|
||||
BINARY_NAME="goecs"
|
||||
if [[ "${{ matrix.goos }}" == "windows" ]]; then
|
||||
BINARY_NAME="${BINARY_NAME}.exe"
|
||||
fi
|
||||
ZIP_NAME="goecs_${{ matrix.goos }}_${{ matrix.goarch }}"
|
||||
if [[ -n "${{ matrix.goarm }}" ]]; then
|
||||
ZIP_NAME="${ZIP_NAME}v${{ matrix.goarm }}"
|
||||
fi
|
||||
if [[ -n "${{ matrix.gomips }}" ]]; then
|
||||
ZIP_NAME="${ZIP_NAME}_${{ matrix.gomips }}"
|
||||
fi
|
||||
ZIP_NAME="${ZIP_NAME}.zip"
|
||||
zip "$ZIP_NAME" "$BINARY_NAME"
|
||||
|
||||
- name: Upload to Release
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
cd bin
|
||||
for file in *.zip; do
|
||||
if [[ -f "$file" ]]; then
|
||||
curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: application/zip" \
|
||||
--data-binary @"$file" \
|
||||
"https://uploads.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=$file"
|
||||
fi
|
||||
done
|
||||
|
||||
checksums:
|
||||
name: Generate Checksums
|
||||
runs-on: ubuntu-latest
|
||||
needs: release-binary
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download release assets
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
mkdir -p assets
|
||||
ASSETS=$(curl -s -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets")
|
||||
echo "$ASSETS" | jq -r '.[] | select(.name | endswith(".zip")) | .browser_download_url' | while read url; do
|
||||
filename=$(basename "$url")
|
||||
curl -L -H "Authorization: Bearer ${{ secrets.GHT }}" "$url" -o "assets/$filename"
|
||||
done
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd assets
|
||||
sha256sum *.zip > checksums.txt
|
||||
if [[ -f "../goecs.sh" ]]; then
|
||||
sha256sum ../goecs.sh >> checksums.txt
|
||||
fi
|
||||
|
||||
- name: Upload checksums
|
||||
run: |
|
||||
TAG="${{ steps.tag.outputs.tag }}"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" "https://api.github.com/repos/${{ github.repository }}/releases/tags/$TAG" | jq -r '.id')
|
||||
curl -s -H "Authorization: Bearer ${{ secrets.GHT }}" \
|
||||
-H "Content-Type: text/plain" \
|
||||
--data-binary @assets/checksums.txt \
|
||||
"https://uploads.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=checksums.txt"
|
||||
|
||||
update-script:
|
||||
name: Update Script Version
|
||||
runs-on: ubuntu-latest
|
||||
needs: checksums
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest tag
|
||||
id: tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.1.0")
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "version=${TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update goecs.sh version
|
||||
run: |
|
||||
VERSION="${{ steps.tag.outputs.version }}"
|
||||
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 "goecs.sh" ]; then
|
||||
sed -i "s/\(_yellow \"Unable to get version info, using default version \).*\(\".*\)/\1$VERSION\2/" "goecs.sh"
|
||||
sed -i "s/\(ECS_VERSION=\"\).*\(\"\)/\1$VERSION\2/" "goecs.sh"
|
||||
if ! git diff --quiet "goecs.sh"; then
|
||||
git add "goecs.sh"
|
||||
git commit -m "chore: update ECS_VERSION to $VERSION in goecs.sh"
|
||||
git push origin $BRANCH
|
||||
fi
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
@@ -1,11 +0,0 @@
|
||||
package commediatest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/oneclickvirt/CommonMediaTests/commediatests"
|
||||
)
|
||||
|
||||
func ComMediaTest(language string) {
|
||||
res := commediatests.MediaTests(language)
|
||||
fmt.Print(res)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package commediatest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 本包仅测试无实际使用
|
||||
func TestMedia(t *testing.T) {
|
||||
ComMediaTest("zh")
|
||||
}
|
||||
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()
|
||||
@@ -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.Print(ipInfo)
|
||||
fmt.Println("--------------------------------------------------")
|
||||
fmt.Print(securityInfo)
|
||||
fmt.Println("--------------------------------------------------")
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
31
.github/workflows/build_binary.yaml
vendored
31
.github/workflows/build_binary.yaml
vendored
@@ -19,11 +19,31 @@ jobs:
|
||||
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.24.5
|
||||
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/"
|
||||
@@ -31,16 +51,21 @@ jobs:
|
||||
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
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
GOPRIVATE: github.com/oneclickvirt/security
|
||||
GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest
|
||||
|
||||
- name: Update goecs.sh with new version
|
||||
run: |
|
||||
|
||||
50
.github/workflows/build_public.yml
vendored
50
.github/workflows/build_public.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24.5'
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Update master branch README files
|
||||
run: |
|
||||
@@ -40,7 +40,8 @@ jobs:
|
||||
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 README_EN.md
|
||||
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
|
||||
@@ -54,47 +55,22 @@ jobs:
|
||||
|
||||
- name: Create public branch
|
||||
run: |
|
||||
git checkout -b public || git checkout public
|
||||
git merge ${{ github.ref_name }} --no-edit || true
|
||||
# 删除本地 public 分支(如果存在)
|
||||
git branch -D public 2>/dev/null || true
|
||||
# 基于当前分支创建新的 public 分支(完全覆盖)
|
||||
git checkout -b public
|
||||
|
||||
- name: Remove security package references
|
||||
run: |
|
||||
find . -type f -name "*.go" -exec sed -i 's|"github.com/oneclickvirt/security/network"|"github.com/oneclickvirt/basics/network"|g' {} +
|
||||
sed -i '/SecurityUploadToken/d' utils/utils.go
|
||||
sed -i 's|"github.com/oneclickvirt/security/network"|"github.com/oneclickvirt/basics/network"|g' utils/utils.go
|
||||
sed -i '/^import/,/^)/{/^)/a\'$'\n''const token = "OvwKx5qgJtf7PZgCKbtyojSU.MTcwMTUxNzY1MTgwMw"'$'\n''}' utils/utils.go
|
||||
sed -i '/github.com\/oneclickvirt\/security/d' go.mod
|
||||
sed -i 's|var securityFlag = flag.Bool("security", true,|var securityFlag = flag.Bool("security", false,|g' goecs.go
|
||||
sed -i 's|VPS融合怪测试|VPS融合怪测试(非官方编译)|g' utils/utils.go
|
||||
sed -i 's|VPS Fusion Monster Test|VPS Fusion Monster Test (Unofficial)|g' utils/utils.go
|
||||
python3 .back/create_public_branch.py
|
||||
rm -f go.sum
|
||||
go clean -modcache
|
||||
go clean -cache -testcache -fuzzcache
|
||||
go mod tidy
|
||||
sed -i 's|但二进制文件编译至 \[securityCheck\].*)|但已开源|g' README.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.md
|
||||
sed -i 's|security.*Enable/Disable security test (default true)|security Enable/Disable security test (default false)|g' README_EN.md
|
||||
- name: Update Go version in README files
|
||||
run: |
|
||||
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
|
||||
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
|
||||
echo "已更新 README_EN.md"
|
||||
fi
|
||||
else
|
||||
echo "错误:未能提取到有效的 Go 版本号或版本号格式不正确"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "错误:未找到 go.mod 文件"
|
||||
exit 1
|
||||
fi
|
||||
# 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
|
||||
|
||||
3
.github/workflows/sync.yaml
vendored
3
.github/workflows/sync.yaml
vendored
@@ -32,8 +32,9 @@ jobs:
|
||||
- name: Copy repository files
|
||||
run: |
|
||||
cp goecs.sh temp_repo/
|
||||
cp README_EN.md 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
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,2 +1,5 @@
|
||||
vendor/
|
||||
.idea/
|
||||
.idea/
|
||||
ecs
|
||||
goecs.txt
|
||||
*.log
|
||||
@@ -1,6 +1,10 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -v
|
||||
- go clean -cache
|
||||
|
||||
project_name: goecs
|
||||
|
||||
builds:
|
||||
- id: universal
|
||||
env:
|
||||
@@ -31,6 +35,7 @@ builds:
|
||||
goarch: arm
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
@@ -44,6 +49,7 @@ builds:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
@@ -57,18 +63,23 @@ builds:
|
||||
- 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:
|
||||
@@ -79,4 +90,18 @@ changelog:
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
- go mod tidy
|
||||
- New translations
|
||||
- New translations
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
brute: true
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm64
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
24
Dockerfile
24
Dockerfile
@@ -1,10 +1,24 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM alpine:latest
|
||||
# 安装必要的工具
|
||||
RUN apk add --no-cache wget curl bash
|
||||
RUN apk add --no-cache bind-tools
|
||||
# --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
|
||||
RUN apk add --no-cache grep openssl ca-certificates uuidgen
|
||||
|
||||
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 && \
|
||||
|
||||
300
README.md
300
README.md
@@ -6,179 +6,184 @@
|
||||
|
||||
[](https://hits.spiritlhl.net) [](https://github.com/oneclickvirt/ecs/releases)
|
||||
|
||||
融合怪测评项目 - GO版本
|
||||
Fusion Monster Evaluation Project - GO Version
|
||||
|
||||
(仅环境安装[非必须]使用shell外无额外shell文件依赖,环境安装只是为了测的更准,极端情况下无环境依赖安装也可全测项目)
|
||||
(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)
|
||||
|
||||
如有问题请 [issues](https://github.com/oneclickvirt/ecs/issues) 反馈。
|
||||
Please report any issues via [issues](https://github.com/oneclickvirt/ecs/issues).
|
||||
|
||||
Go 版本:[https://github.com/oneclickvirt/ecs](https://github.com/oneclickvirt/ecs)
|
||||
Go version: [https://github.com/oneclickvirt/ecs](https://github.com/oneclickvirt/ecs)
|
||||
|
||||
Shell 版本:[https://github.com/spiritLHLS/ecs](https://github.com/spiritLHLS/ecs)
|
||||
Shell version: [https://github.com/spiritLHLS/ecs/blob/main/README_EN.md](https://github.com/spiritLHLS/ecs/blob/main/README_EN.md)
|
||||
|
||||
---
|
||||
|
||||
## **语言**
|
||||
## **Language**
|
||||
|
||||
[中文文档](README.md) | [English Docs](README_EN.md)
|
||||
[English Docs](README.md) | [中文文档](README_ZH.md)
|
||||
|
||||
---
|
||||
|
||||
## **适配系统和架构**
|
||||
## **Supported Systems and Architectures**
|
||||
|
||||
### **编译与测试支持情况**
|
||||
| 编译支持的架构 | 测试支持的架构 | 编译支持的系统 | 测试支持的系统 |
|
||||
|---------------------------|--------------|---------------------------|---------------|
|
||||
| amd64 | amd64 | Linux | Linux |
|
||||
| arm64 | arm64 | Windows | Windows |
|
||||
| arm | | MacOS(Darwin) | MacOS |
|
||||
| 386 | | FreeBSD | |
|
||||
| mips,mipsle | | Android | |
|
||||
| mips64,mips64le | | | |
|
||||
| ppc64,ppc64le | | | |
|
||||
| s390x | s390x | | |
|
||||
| riscv64 | | | |
|
||||
### **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 | | | |
|
||||
|
||||
> 更多架构与系统请自行测试或编译,如有问题请开 issues。
|
||||
> 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**
|
||||
|
||||
| 系统 | 说明 |
|
||||
|----------------|---------------------------|
|
||||
| Android(arm64) | 存在权限问题未修复,非安卓系统的ARM架构无问题 |
|
||||
| OpenBSD/NetBSD | 部分Goalng的官方库未支持本系统(尤其是net相关项目) |
|
||||
| OS | Notes |
|
||||
|--------|-------------------------------------------------------------------------------------------------|
|
||||
| OpenBSD/NetBSD | Some of Golang's official libraries do not support this system (especially net-related items) |
|
||||
|
||||
---
|
||||
|
||||
## **功能**
|
||||
## **Features**
|
||||
|
||||
- 系统基础信息查询,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
|
||||
- 流媒体解锁信息并发查询:[netflix-verify](https://github.com/sjlleo/netflix-verify) 等逻辑,开发至 [CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests)
|
||||
- 常见流媒体测试并发查询:[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的在线环境下进行测试
|
||||
- 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
|
||||
|
||||
**本项目初次使用建议查看说明:[跳转](https://github.com/oneclickvirt/ecs/blob/master/README_NEW_USER.md)**
|
||||
**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 env && ./goecs.sh install && goecs
|
||||
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
|
||||
```
|
||||
|
||||
- **国际/国内使用 CDN 加速:**
|
||||
- **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 env && ./goecs.sh install && goecs
|
||||
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
|
||||
```
|
||||
|
||||
- **国内用户使用 CNB 加速:**
|
||||
- **Domestic users with CNB acceleration:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh env && ./goecs.sh install && goecs
|
||||
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 && ./goecs.sh env && ./goecs.sh install && goecs
|
||||
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>展开查看详细说明</summary>
|
||||
<summary>Expand to view detailed instructions</summary>
|
||||
|
||||
1. **下载脚本**
|
||||
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
|
||||
```
|
||||
|
||||
**国际/国内使用 CDN 加速:**
|
||||
**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
|
||||
```
|
||||
|
||||
**国内用户使用 CNB 加速:**
|
||||
**Domestic users with CNB acceleration:**
|
||||
|
||||
```bash
|
||||
curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
export CN=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh
|
||||
```
|
||||
|
||||
2. **更新包管理器(可选择)并安装环境**
|
||||
2. **Update package manager (optional) and install environment**
|
||||
|
||||
```bash
|
||||
./goecs.sh env
|
||||
```
|
||||
|
||||
**非互动模式:**
|
||||
**Non-interactive mode:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && ./goecs.sh env
|
||||
```
|
||||
|
||||
3. **安装 `goecs` 本体(仅下载二进制文件无依赖安装)**
|
||||
3. **Install `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh install
|
||||
```
|
||||
|
||||
4. **升级 `goecs` 本体**
|
||||
4. **Upgrade `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh upgrade
|
||||
```
|
||||
|
||||
5. **卸载 `goecs` 本体**
|
||||
5. **Uninstall `goecs`**
|
||||
|
||||
```bash
|
||||
./goecs.sh uninstall
|
||||
```
|
||||
|
||||
6. **帮助命令**
|
||||
6. **help command**
|
||||
|
||||
```bash
|
||||
./goecs.sh -h
|
||||
```
|
||||
|
||||
7. **唤起菜单**
|
||||
7. **Invoke the menu**
|
||||
|
||||
```bash
|
||||
goecs
|
||||
goecs -l=en
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
#### **命令参数化**
|
||||
#### **Command parameterization**
|
||||
|
||||
<details>
|
||||
<summary>展开查看各参数说明</summary>
|
||||
<summary>Expand to view parameter descriptions</summary>
|
||||
|
||||
```bash
|
||||
Usage: goecs [options]
|
||||
@@ -186,18 +191,24 @@ Usage: goecs [options]
|
||||
Enable/Disable backtrace test (in 'en' language or on windows it always false) (default true)
|
||||
-basic
|
||||
Enable/Disable basic test (default true)
|
||||
-comm
|
||||
Enable/Disable common media 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
|
||||
@@ -205,33 +216,49 @@ Usage: goecs [options]
|
||||
-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: sysbench, dd, winsat) (default "sysbench")
|
||||
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 for Guangzhou, Shanghai, Beijing, Chengdu) (default "GZ")
|
||||
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)
|
||||
-ut
|
||||
Enable/Disable unlock media test (default true)
|
||||
-v Display version information
|
||||
-version
|
||||
Display version information
|
||||
-web
|
||||
Enable/Disable popular websites test
|
||||
```
|
||||
</details>
|
||||
|
||||
@@ -239,96 +266,95 @@ Usage: goecs [options]
|
||||
|
||||
### **Windows**
|
||||
|
||||
1. 下载带 exe 文件的压缩包:[Releases](https://github.com/oneclickvirt/ecs/releases)
|
||||
2. 解压后,右键以管理员模式运行。
|
||||
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:如果是虚拟机环境,不以管理员模式运行也行,因为虚拟机无原生的测试工具,将自动启用替代方法测试。
|
||||
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>展开查看使用说明</summary>
|
||||
<summary>Expand to view how to use it</summary>
|
||||
|
||||
国际镜像地址:https://hub.docker.com/r/spiritlhl/goecs
|
||||
International image: https://hub.docker.com/r/spiritlhl/goecs
|
||||
|
||||
请确保执行下述命令前本机已安装Docker
|
||||
Please ensure Docker is installed on your machine before executing the following commands
|
||||
|
||||
特权模式+host网络
|
||||
Privileged mode + host network
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host spiritlhl/goecs:latest -menu=false -l zh
|
||||
docker run --rm --privileged --network host spiritlhl/goecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
非特权模式+非host网络
|
||||
Unprivileged mode + non-host network
|
||||
|
||||
```shell
|
||||
docker run --rm spiritlhl/goecs:latest -menu=false -l zh
|
||||
docker run --rm spiritlhl/goecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
使用Docker执行测试,硬件测试会有一些偏差和虚拟化架构判断失效,还是推荐直接测试而不使用Docker测试。
|
||||
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
|
||||
|
||||
请确保执行下述命令前本机已安装Docker
|
||||
Please ensure Docker is installed on your machine before executing the following commands
|
||||
|
||||
特权模式+host网络
|
||||
Privileged mode + host network
|
||||
|
||||
```shell
|
||||
docker run --rm --privileged --network host crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com/oneclickvirt/ecs:latest -menu=false -l zh
|
||||
docker run --rm --privileged --network host docker.cnb.cool/oneclickvirt/ecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
非特权模式+非host网络
|
||||
Unprivileged mode + non-host network
|
||||
|
||||
```shell
|
||||
docker run --rm crpi-8tmognxgyb86bm61.cn-guangzhou.personal.cr.aliyuncs.com/oneclickvirt/ecs:latest -menu=false -l zh
|
||||
docker run --rm docker.cnb.cool/oneclickvirt/ecs:latest -menu=false -l=en
|
||||
```
|
||||
|
||||
实际上还有CNB镜像地址 https://cnb.cool/oneclickvirt/ecs/-/packages/docker/ecs 但很可惜组织空间不足无法推送了,更推荐使用阿里云镜像加速
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
### 从源码进行编译
|
||||
### Compiling from source code
|
||||
|
||||
<details>
|
||||
<summary>展开查看编译说明</summary>
|
||||
<summary>Expand to view compilation instructions</summary>
|
||||
|
||||
1. 克隆仓库的 public 分支(不含私有依赖)
|
||||
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. 安装 Go 环境(如已安装可跳过)
|
||||
2. Install Go environment (skip if already installed)
|
||||
|
||||
选择 go 1.24.5 的版本进行安装
|
||||
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. 编译
|
||||
3. Compile
|
||||
```bash
|
||||
go build -o goecs
|
||||
```
|
||||
|
||||
4. 运行测试
|
||||
4. Run test
|
||||
```bash
|
||||
./goecs -menu=false -l zh
|
||||
./goecs -menu=false -l=en
|
||||
```
|
||||
|
||||
支持的编译参数:
|
||||
- GOOS:支持 linux、windows、darwin、freebsd、openbsd
|
||||
- GOARCH:支持 amd64、arm、arm64、386、mips、mipsle、s390x、riscv64
|
||||
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
|
||||
# 编译 Windows 版本
|
||||
# Compile Windows version
|
||||
GOOS=windows GOARCH=amd64 go build -o goecs.exe
|
||||
# 编译 MacOS 版本
|
||||
# Compile MacOS version
|
||||
GOOS=darwin GOARCH=amd64 go build -o goecs_darwin
|
||||
```
|
||||
</details>
|
||||
@@ -337,55 +363,55 @@ GOOS=darwin GOARCH=amd64 go build -o goecs_darwin
|
||||
|
||||
## QA
|
||||
|
||||
#### Q: 为什么默认使用sysbench而不是geekbench
|
||||
#### Q: Why is sysbench used by default instead of geekbench?
|
||||
|
||||
#### A: 比较二者特点
|
||||
#### A: Comparing the characteristics of both:
|
||||
|
||||
| 比较项 | sysbench | geekbench |
|
||||
|------------------|----------|-----------|
|
||||
| 适用范围 | 轻量级,几乎可在任何服务器上运行 | 重量级,小型机器无法运行 |
|
||||
| 测试要求 | 无需网络,无特殊硬件需求 | 需联网,IPV4环境,至少1G内存 |
|
||||
| 开源情况 | 基于LUA,开源,可自行编译各架构版本 | 官方二进制闭源代码,不支持自行编译 |
|
||||
| 测试稳定性 | 核心测试组件10年以上未变 | 每个大版本更新测试项,分数不同版本间难以对比(每个版本对标当前最好的CPU) |
|
||||
| 测试内容 | 仅测试计算性能 | 覆盖多种性能测试,分数加权计算,但部分测试实际不常用 |
|
||||
| 适用场景 | 适合快速测试,仅测试计算性能 | 适合综合全面的测试 |
|
||||
| 排行榜 | [sysbench.spiritlhl.net](https://sysbench.spiritlhl.net/) | [browser.geekbench.com](https://browser.geekbench.com/) |
|
||||
| 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/) |
|
||||
|
||||
且```goecs```测试使用何种CPU测试方式可使用参数指定,默认只是为了更多用户快速测试的需求
|
||||
Note that `goecs` allows you to specify CPU test method via parameters. The default is chosen for faster testing across more systems.
|
||||
|
||||
#### Q: 为什么使用Golang而不是Rust重构
|
||||
#### Q: Why use Golang instead of Rust for refactoring?
|
||||
|
||||
#### A: 因为网络相关的项目目前以Golang语言为趋势,大多组件有开源生态维护,Rust很多得自己手搓,~~我懒得搞~~我没那个技术力
|
||||
#### 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: 为什么不继续开发Shell版本而是选择重构
|
||||
#### Q: Why not continue developing the Shell version instead of refactoring?
|
||||
|
||||
#### A: 因为太多千奇百怪的环境问题了,还是提前编译好测试的二进制文件比较容易解决环境问题(泛化性更好)
|
||||
#### A: Because there were too many varied environment issues. Pre-compiled binary files are easier for solving environment problems (better generalization).
|
||||
|
||||
#### Q: 每个测试项目的说明有吗?
|
||||
#### Q: Are there explanations for each test item?
|
||||
|
||||
#### A: 每个测试项目有对应的维护仓库,自行点击查看仓库说明
|
||||
#### A: Each test project has its own maintenance repository. Click through to view the repository description.
|
||||
|
||||
#### Q: 测试进行到一半如何手动终止?
|
||||
#### Q: How do I manually terminate a test halfway through?
|
||||
|
||||
#### A: 按ctrl键和c键终止程序,终止后依然会在当前目录下生成goecs.txt文件和分享链接,里面是已经测试到的信息。
|
||||
#### 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: 非Root环境如何进行测试?
|
||||
#### Q: How do I test in a non-Root environment?
|
||||
|
||||
#### A: 手动执行安装命令,实在装不上也没问题,直接在release中下载对应架构的压缩包解压后执行即可,只要你能执行的了文件。或者你能使用docker的话用docker执行。
|
||||
#### 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
|
||||
|
||||
感谢 [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) 等网站提供的API进行检测,感谢互联网各网站提供的查询资源
|
||||
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;">
|
||||
|
||||
408
README_EN.md
408
README_EN.md
@@ -1,408 +0,0 @@
|
||||
# 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)
|
||||
|
||||
Fusion Monster Evaluation Project - GO Version
|
||||
|
||||
(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)
|
||||
|
||||
Please report any issues via [issues](https://github.com/oneclickvirt/ecs/issues).
|
||||
|
||||
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**
|
||||
|
||||
[中文文档](README.md) | [English Docs](README_EN.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 |
|
||||
|--------|-------------------------------------------------------------------------------------------------|
|
||||
| Android(arm64) | Permission issues that are not fixed, no problems with ARM architecture for non-Android systems |
|
||||
| OpenBSD/NetBSD | Some of Goalng'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 media unlock information concurrent query: Modified from [netflix-verify](https://github.com/sjlleo/netflix-verify) and more to [CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests)
|
||||
- Common streaming media 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 **Install Dependencies by Default**, **Update Package Manager by Default**, **Default Non-Interactive Mode***
|
||||
|
||||
- **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 env && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **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 env && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **Domestic users with CNB acceleration:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://cnb.cool/oneclickvirt/ecs/-/git/raw/main/goecs.sh -o goecs.sh && chmod +x goecs.sh && ./goecs.sh env && ./goecs.sh install && goecs
|
||||
```
|
||||
|
||||
- **Short Link:**
|
||||
|
||||
```bash
|
||||
export noninteractive=true && curl -L https://bash.spiritlhl.net/goecs -o goecs.sh && chmod +x goecs.sh && bash goecs.sh env && bash goecs.sh install && goecs
|
||||
``
|
||||
|
||||
#### **Detailed instructions**
|
||||
|
||||
**Detailed description** of the commands in **Command **Controls whether to install dependencies**, **Whether to update the package manager**, **Default interaction mode can be selected***
|
||||
|
||||
<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
|
||||
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)
|
||||
-comm
|
||||
Enable/Disable common media test (default true)
|
||||
-cpu
|
||||
Enable/Disable CPU test (default true)
|
||||
-cpum string
|
||||
Set CPU test method (supported: sysbench, geekbench, winsat) (default "sysbench")
|
||||
-cput 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")
|
||||
-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
|
||||
-l 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: sysbench, dd, winsat) (default "sysbench")
|
||||
-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 for Guangzhou, Shanghai, Beijing, Chengdu) (default "GZ")
|
||||
-nt3t string
|
||||
Set NT3 test type (supported: both, ipv4, ipv6) (default "ipv4")
|
||||
-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)
|
||||
-upload
|
||||
Enable/Disable upload the result (default true)
|
||||
-ut
|
||||
Enable/Disable unlock media test (default true)
|
||||
-v Display version information
|
||||
```
|
||||
</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.
|
||||
|
||||
---
|
||||
|
||||
### **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.24.5 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)
|
||||
@@ -3,46 +3,55 @@
|
||||
[](https://hits.spiritlhl.net) [](https://github.com/oneclickvirt/ecs/releases)
|
||||
|
||||
## 语言 / Languages / 言語
|
||||
|
||||
- [中文](#中文)
|
||||
- [English](#English)
|
||||
- [日本語](#日本語)
|
||||
|
||||
## 中文
|
||||
|
||||
- [系统基础信息](#系统基础信息)
|
||||
- [CPU测试](#CPU测试)
|
||||
- [内存测试](#内存测试)
|
||||
- [硬盘测试](#硬盘测试)
|
||||
- [流媒体解锁](#流媒体解锁)
|
||||
- [平台解锁检测](#平台解锁检测)
|
||||
- [IP质量检测](#IP质量检测)
|
||||
- [邮件端口检测](#邮件端口检测)
|
||||
- [上游及回程线路检测](#上游及回程线路检测)
|
||||
- [三网回程路由检测](#三网回程路由检测)
|
||||
- [PING值测试](#PING值测试)
|
||||
- [就近测速](#就近测速)
|
||||
|
||||
## English
|
||||
|
||||
- [Basic System Information](#Basic-System-Information)
|
||||
- [CPU Testing](#CPU-Testing)
|
||||
- [Memory Testing](#Memory-Testing)
|
||||
- [Disk Testing](#Disk-Testing)
|
||||
- [Streaming Media Unlocking](#Streaming-Media-Unlocking)
|
||||
- [Platform Unlock Testing](#Platform-Unlock-Testing)
|
||||
- [IP Quality Detection](#IP-Quality-Detection)
|
||||
- [Email Port Detection](#Email-Port-Detection)
|
||||
- [PING Testing](#PING-Testing)
|
||||
- [Nearby Speed Testing](#Nearby-Speed-Testing)
|
||||
|
||||
## 日本語
|
||||
|
||||
- [システム基本情報](#システム基本情報)
|
||||
- [CPUテスト](#CPUテスト)
|
||||
- [メモリテスト](#メモリテスト)
|
||||
- [ディスクテスト](#ディスクテスト)
|
||||
- [ストリーミングメディアロック解除](#ストリーミングメディアロック解除)
|
||||
- [プラットフォームロック解除検出](#プラットフォームロック解除検出)
|
||||
- [IP品質検出](#IP品質検出)
|
||||
- [メールポート検出](#メールポート検出)
|
||||
- [PING検出](#PING検出)
|
||||
- [近隣スピードテスト](#近隣スピードテスト)
|
||||
|
||||
---
|
||||
|
||||
## 中文
|
||||
|
||||
menu模式默认启用,执行时显示菜单可选择选项测试,在menu模式启用的情况下,默认额外提供的CI参数设置优先级高于选项本身的预设值,方便用户随时针对某个选项自行修改某些单项测试的参数设置。
|
||||
|
||||
### **系统基础信息**
|
||||
|
||||
依赖项目:[https://github.com/oneclickvirt/basics](https://github.com/oneclickvirt/basics) [https://github.com/oneclickvirt/gostun](https://github.com/oneclickvirt/gostun)
|
||||
@@ -213,24 +222,24 @@ AMD的7950x单核满血性能得分在6500左右,AMD的5950x单核满血性能
|
||||
|
||||
注意,这里测试的是真实的IO,仅限本项目,非本项目测试的IO不保证基准通用,因为他们测试的时候可能用的不是同样的参数,可能未设置IO直接读写,可能设置IO引擎不一致,可能设置测试时间不一致,都会导致基准有偏差。
|
||||
|
||||
### **流媒体解锁**
|
||||
### **平台解锁检测**
|
||||
|
||||
依赖项目:[https://github.com/oneclickvirt/CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests) [https://github.com/oneclickvirt/UnlockTests](https://github.com/oneclickvirt/UnlockTests)
|
||||
依赖项目:[https://github.com/oneclickvirt/UnlockTests](https://github.com/oneclickvirt/UnlockTests)
|
||||
|
||||
默认只检测跨国流媒体解锁。
|
||||
默认只检测跨国平台解锁。
|
||||
|
||||
一般来说,正常的情况下,一个IP多个流媒体的解锁地区都是一致的不会到处乱飘,如果发现多家平台解锁地区不一致,那么IP大概率来自IPXO等平台租赁或者是刚刚宣告和被使用,未被流媒体普通的数据库所识别修正地域。
|
||||
一般来说,正常的情况下,一个IP多个平台的解锁地区都是一致的不会到处乱飘,如果发现多家平台解锁地区不一致,那么IP大概率来自IPXO等平台租赁或者是刚刚宣告和被使用,未被平台普通的数据库所识别修正地域。
|
||||
|
||||
由于各平台的IP数据库识别速度不一致,所以有时候有的平台解锁区域正常,有的飘到路由上的某个位置,有的飘到IP未被你使用前所在的位置。
|
||||
|
||||
| DNS 类型 | 解锁方式判断是否必要 | DNS 对解锁影响 | 说明 |
|
||||
| ------------ | ---------- | --------- | --------------------------------------- |
|
||||
| 官方主流 DNS | 否 | 小 | 流媒体解锁主要依赖测试节点的 IP,DNS 解析基本不会干扰解锁。 |
|
||||
| 非主流 / 自建 DNS | 是 | 大 | 流媒体解锁结果受 DNS 解析影响较大,需要判断是原生解锁还是 DNS 解锁。|
|
||||
| 官方主流 DNS | 否 | 小 | 平台解锁主要依赖测试节点的 IP,DNS 解析基本不会干扰解锁。 |
|
||||
| 非主流 / 自建 DNS | 是 | 大 | 平台解锁结果受 DNS 解析影响较大,需要判断是原生解锁还是 DNS 解锁。|
|
||||
|
||||
所以测试过程中,如果宿主机当前使用的是官方主流的DNS,不会进行是否为原生解锁的判断,解锁类型大部分受后面查询的IP质量的使用类型和公司类型的影响。
|
||||
|
||||
对于IP质量解锁比较敏感的实际上是各大AI平台和本地流媒体,以及reddit和spotify,其他的跨国平台一般不易受IP质量影响解锁。
|
||||
对于IP质量解锁比较敏感的实际上是各大AI平台和本地平台解锁,以及reddit和spotify,其他的跨国平台一般不易受IP质量影响解锁。
|
||||
|
||||
### **IP质量检测**
|
||||
|
||||
@@ -238,53 +247,55 @@ AMD的7950x单核满血性能得分在6500左右,AMD的5950x单核满血性能
|
||||
|
||||
检测18个数据库的IP相关信息,多个平台比较对应检测项目都为对应值,证明当前IP确实如此,不要仅相信一个数据库源的信息:
|
||||
|
||||
[ipinfo.io](https://ipinfo.io) [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)
|
||||
[ipinfo.io](https://ipinfo.io) [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) [cloudflare.com](https://www.cloudflare.com/)
|
||||
|
||||
以下为每个字段的对应的含义
|
||||
|
||||
| 字段类别 | 字段名称 | 字段说明 | 可能的值 | 评分规则 |
|
||||
|---------|---------|---------|---------|---------|
|
||||
| 安全得分 | 声誉(Reputation) | IP地址在安全社区中的信誉评分 | 0-100的数值 | 越高越好 |
|
||||
| | 信任得分(Trust Score) | IP地址的可信任程度评分 | 0-100的数值 | 越高越好 |
|
||||
| | VPN得分(VPN Score) | IP被识别为VPN的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| | 代理得分(Proxy Score) | IP被识别为代理的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| 安全得分 | 声誉 | IP地址在安全社区中的信誉评分 | 0-100的数值 | 越高越好 |
|
||||
| | 信任得分 | IP地址的可信任程度评分 | 0-100的数值 | 越高越好 |
|
||||
| | VPN得分 | IP被识别为VPN的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| | 代理得分 | IP被识别为代理的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| | 社区投票-无害 | 社区成员投票认为该IP无害的分数 | 非负整数 | 越高越好 |
|
||||
| | 社区投票-恶意 | 社区成员投票认为该IP恶意的分数 | 非负整数 | 越低越好 |
|
||||
| | 威胁得分(Threat Score) | IP地址的整体威胁程度评分 | 0-100的数值 | 越低越好 |
|
||||
| | 欺诈得分(Fraud Score) | IP地址涉及欺诈活动的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| | 滥用得分(Abuse Score) | IP地址被报告滥用行为的评分 | 0-100的数值 | 越低越好 |
|
||||
| | 威胁得分 | IP地址的整体威胁程度评分 | 0-100的数值 | 越低越好 |
|
||||
| | 欺诈得分 | IP地址涉及欺诈活动的可能性评分 | 0-100的数值 | 越低越好 |
|
||||
| | 滥用得分 | IP地址被报告滥用行为的评分 | 0-100的数值 | 越低越好 |
|
||||
| | ASN滥用得分 | 该IP所属ASN(自治系统)的滥用评分 | 0-1的小数,可能带有风险等级标注(Low/Medium/High) | 越低越好 |
|
||||
| | 公司滥用得分 | 该IP所属公司的滥用评分 | 0-1的小数,可能带有风险等级标注(Low/Medium/High) | 越低越好 |
|
||||
| | 威胁级别(Threat Level) | IP地址的威胁等级分类 | low/medium/high/critical等文本描述 | low为最佳 |
|
||||
| 黑名单记录 | 无害记录数(Harmless) | 在各黑名单数据库中被标记为无害的次数 | 非负整数 | 数值本身无好坏 |
|
||||
| | 恶意记录数(Malicious) | 在各黑名单数据库中被标记为恶意的次数 | 非负整数 | 越低越好 |
|
||||
| | 可疑记录数(Suspicious) | 在各黑名单数据库中被标记为可疑的次数 | 非负整数 | 越低越好 |
|
||||
| | 无记录数(Undetected) | 在各黑名单数据库中无任何记录的次数 | 非负整数 | 数值本身无好坏 |
|
||||
| | 威胁级别 | IP地址的威胁等级分类 | low/medium/high/critical等文本描述 | low为最佳 |
|
||||
| | 流量占比 | 真人和机器人在本机IP的ASN所在国家的占比 | 百分数 | 真人比越高越好 |
|
||||
| 黑名单记录 | 无害记录数 | 在各黑名单数据库中被标记为无害的次数 | 非负整数 | 数值本身无好坏 |
|
||||
| | 恶意记录数 | 在各黑名单数据库中被标记为恶意的次数 | 非负整数 | 越低越好 |
|
||||
| | 可疑记录数 | 在各黑名单数据库中被标记为可疑的次数 | 非负整数 | 越低越好 |
|
||||
| | 无记录数 | 在各黑名单数据库中无任何记录的次数 | 非负整数 | 数值本身无好坏 |
|
||||
| | DNS黑名单-总检查数 | 检查的DNS黑名单数据库总数量 | 正整数 | 数值本身无好坏 |
|
||||
| | DNS黑名单-干净 | 在DNS黑名单中显示为干净(未列入)的数量 | 非负整数 | 越高越好 |
|
||||
| | DNS黑名单-已列入 | 在DNS黑名单中已被列入的数量 | 非负整数 | 越低越好 |
|
||||
| | DNS黑名单-其他 | 在DNS黑名单检查中返回其他状态的数量 | 非负整数 | 数值本身无好坏 |
|
||||
|
||||
当本机的IP所在的ASN拥有的IP数量比较少时,流量占比可以给你提供网络邻居中有多少是真实流量的占比,目前全球的互联网流量的占比约是70%真人30%机器人(IPV4),如果需要比较你就按这个基准对比就行了,就知道是不是当前IP的ASN在本国的使用达到了互联网平均水平。如果需要具体国家的基准查询,可使用 https://trafficbenchmark.spiritlhl.net/ 自行搜索查找对比。
|
||||
|
||||
一般来说看下面的使用类型公司类型还有安全信息的判别足矣,上面的安全得分只有多个数据库确认一致才可信,不看也没啥问题。(IDC: 一般买服务器识别成这个的多,就是正常的在数据中心机房广播使用的类型)
|
||||
|
||||
| 使用类型 | 说明 |
|
||||
| ----------- | ---------- |
|
||||
| hosting | 数据中心网络(IDC) |
|
||||
| residential | 家庭/住宅网络(家宽) |
|
||||
| FixedLineISP,ISP | 固定线路互联网服务提供商(家宽) |
|
||||
| isp | 固定线路互联网服务提供商(家宽) |
|
||||
| business | 企业办公网络(商宽) |
|
||||
| cellular | 移动运营商网络(家宽) |
|
||||
| education | 教育机构网络(教育网) |
|
||||
| government | 政府机构网络(政府网) |
|
||||
| military | 军事网络(政府网) |
|
||||
| DataCenter/WebHosting/Transit | 数据中心网络(IDC) |
|
||||
| CDN | 内容分发网络(IDC) |
|
||||
|
||||
| 公司类型 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| business | 企业公司(商宽) |
|
||||
| hosting | 主机/数据中心公司(IDC) |
|
||||
| FixedLineISP,ISP | 固定线路互联网服务提供商(家宽) |
|
||||
| business | 企业公司(商宽) |
|
||||
| isp | 固定线路互联网服务提供商(家宽) |
|
||||
| education | 教育机构(教育网) |
|
||||
| government | 政府机构(政府网) |
|
||||
|
||||
@@ -292,6 +303,9 @@ AMD的7950x单核满血性能得分在6500左右,AMD的5950x单核满血性能
|
||||
|
||||
| 字段类别 | 字段名称 | 字段说明 | 可能的值 | 评分规则 |
|
||||
|---------|---------|---------|---------|---------|
|
||||
| 浏览器类型 | 是否为主流浏览器 | 本机IP的ASN所在国家的占比 | 百分比 | 主流的越多越好 |
|
||||
| 设备类型 | 是否为桌面移动设备 | 本机IP的ASN所在国家的占比 | 百分比 | 桌面移动设备占比越多越好 |
|
||||
| 操作系统类型 | 是否为主流操作系统 | 本机IP的ASN所在国家的占比 | 百分比 | 主流的越多越好 |
|
||||
| 云提供商 | 是否云提供商(Cloud Provider) | 该IP是否属于云服务提供商 | Yes/No | 无好坏之分,仅标识 |
|
||||
| 数据中心 | 是否数据中心(Data Center) | 该IP是否位于数据中心 | Yes/No | 如果关注解锁No为最佳 |
|
||||
| 移动设备 | 是否移动设备(Mobile) | 该IP是否来自移动设备网络 | Yes/No | 如果关注解锁Yes为最佳 |
|
||||
@@ -317,11 +331,11 @@ Abuser 或 Abuse 的滥用得分会直接影响机器的正常使用(中国境
|
||||
- 发起大规模洪流攻击
|
||||
- 进行端口扫描或全网扫描
|
||||
|
||||
这类历史记录会被举报并录入 Abuse 数据库。如果你接手的 IP 刚被他人滥用过,可能仍会有延迟的 Abuse 警告邮件发送至服务商。服务商可能会误判为你本人从事恶意行为,进而清退机器,且大多数情况下无法退款。对跨国流媒体服务而言,Abuse 滥用得分还可能影响平台对该 IP 的信誉评分。
|
||||
这类历史记录会被举报并录入 Abuse 数据库。如果你接手的 IP 刚被他人滥用过,可能仍会有延迟的 Abuse 警告邮件发送至服务商。服务商可能会误判为你本人从事恶意行为,进而清退机器,且大多数情况下无法退款。对跨国平台服务而言,Abuse 滥用得分还可能影响平台对该 IP 的信誉评分。
|
||||
|
||||
对于需要家宽进行流媒体解锁需求的用户(如电商需求),应关注「使用类型」与「公司类型」是否同时识别为 ISP。如果仅为单 ISP 或识别为非 ISP,则后续数据库更新后,IP 类型很可能被更正为 Hosting,从而影响解锁效果。
|
||||
对于需要家宽进行平台解锁需求的用户(如电商需求),应关注「使用类型」与「公司类型」是否同时识别为 ISP。如果仅为单 ISP 或识别为非 ISP,则后续数据库更新后,IP 类型很可能被更正为 Hosting,从而影响解锁效果。
|
||||
|
||||
大部分 IP 识别数据库按月更新。更新后,IP 属性可能被修改,出现由 ISP → Hosting 的情况。对于一些敏感的平台,比如某些特定国家的流媒体(如 Netflix,Spotify),某些区别对待不同国家的流媒体(如 TikTok),非家宽解锁的可能性较低但不是没有,如果你需要稳定解锁且追求其特殊功能解锁,才需要追求家宽流媒体解锁。如果仅仅是浏览观看,很多时候没必要追求家宽,
|
||||
大部分 IP 识别数据库按月更新。更新后,IP 属性可能被修改,出现由 ISP → Hosting 的情况。对于一些敏感的平台,比如某些特定国家的平台(如 Netflix,Spotify),某些区别对待不同国家的平台(如 TikTok),非家宽解锁的可能性较低但不是没有,如果你需要稳定解锁且追求其特殊功能解锁,才需要追求家宽平台解锁。如果仅仅是浏览观看,很多时候没必要追求家宽,
|
||||
|
||||
对于 IP 类型分类有必要仔细说说
|
||||
|
||||
@@ -389,9 +403,15 @@ Abuser 或 Abuse 的滥用得分会直接影响机器的正常使用(中国境
|
||||
|
||||
然后是检测当前的宿主机的IP地址 到 四个主要POP点城市的三个主要运营商的接入点的IP地址 的线路,具体来说
|
||||
|
||||
电信163、联通4837、移动CMI 是常见的线路,移动CMI对两广地区的移动运营商特供延迟低,也能算优质,仅限两广移动。
|
||||
|
||||
电信CN2GIA > 电信CN2GT 移动CMIN2 联通9929 算优质的线路
|
||||
| 运营商 | 线路代号 | 全称 | 特点 | 线路质量 |
|
||||
| --- | -------------- | --------------------------------- | -------------- | --- |
|
||||
| 中国电信 | 163 | ChinaNet (原163骨干网) | 普通国际出口,延迟高易绕路 | 一般 |
|
||||
| 中国电信 | CN2 GT | ChinaNet Next Carrying Network (GT) | 较优于163,偶有拥堵 | 良好 |
|
||||
| 中国电信 | CN2 GIA | Global Internet Access(GT) | 直连国际POP,低延迟低丢包 | 优质(最好) |
|
||||
| 中国联通 | 4837 | Unicom International (AS4837) | 常见国际出口,覆盖广 | 一般到良好 |
|
||||
| 中国联通 | 9929 | Unicom Premium / CU-IX | 精品网,直连主要IXP,延迟低 | 优质 |
|
||||
| 中国移动 | CMI (AS58453) | China Mobile International | 节点多,对两广(广东广西)优化好 | 两广良好,其他一般 |
|
||||
| 中国移动 | CMIN2 (AS58807) | China Mobile International N2 | 高质量专线,低延迟低丢包,对标CN2 | 优质 |
|
||||
|
||||
用什么运营商连宿主机的IP就看哪个运营商的线路就行了,具体线路的路由情况,看在下一个检测项看到对应的ICMP检测路由信息。
|
||||
|
||||
@@ -407,12 +427,67 @@ Abuser 或 Abuse 的滥用得分会直接影响机器的正常使用(中国境
|
||||
|
||||
有时候路由信息完全藏起来了,只知道实际使用的延迟低,实际可能也是优质线路只是查不到信息,这就没办法直接识别了。
|
||||
|
||||
这块能看到更多线路的信息了,一般能看到以下线路
|
||||
|
||||
| 运营商 | 线路代号 | 全称来源 | 特点 | 线路质量 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 中国电信 | 163 | ChinaNet (原163骨干网) | 普通国际出口,延迟高易绕路 | 一般 |
|
||||
| 中国电信 | CN2 GT | ChinaNet Next Carrying Network (GT) | 较优于163,偶有拥堵 | 良好 |
|
||||
| 中国电信 | CN2 GIA | Global Internet Access (GIA) | 直连国际POP,低延迟低丢包 | 优质 |
|
||||
| 中国电信 | CN2 BGP | CN2混合BGP(GIA+GT) | 混合路由,性能略低于纯GIA | 良好至优质 |
|
||||
| 中国电信 | CUII | ChinaNet United International Internet | 面向直连美国的专线 | 优质 |
|
||||
| 中国电信 | 163+CUII混线 | 163国内段+国际专线出口 | 价格低,性能一般 | 一般 |
|
||||
| 中国联通 | 169 | China169骨干网 | 老主干网,一般对接4837 | 一般(少部分优质) |
|
||||
| 中国联通 | 4837 | Unicom International (AS4837) | 常见国际出口,覆盖广 | 良好 |
|
||||
| 中国联通 | 9929 | Unicom Premium / CU-IX | 精品网,直连IXP,低延迟 | 优质 |
|
||||
| 中国联通 | 9929+4837混BGP | 混合出口(IDC常见优化) | 性能平衡 | 良好 |
|
||||
| 中国联通 | CUVIP / CU-IX | 联通精品直连IX (港/新/日) | 企业高端专线 | 优质 |
|
||||
| 中国联通 | CUA (AS17621) | 联通亚洲专线 | 东南亚方向优化 | 良好 |
|
||||
| 中国移动 | CMI (AS58453) | China Mobile International | 对接节点多 | 两广良好,其他一般 |
|
||||
| 中国移动 | CMIN | China Mobile International Network (旧版) | 老出口,已被CMI替代 | 一般 |
|
||||
| 中国移动 | CMIN2 (AS58807) | China Mobile International N2 | 低延迟低丢包 | 优质 |
|
||||
| 中国移动 | CMIN2+CMI混线 | 混合出口 | 依地区表现不同 | 良好 |
|
||||
| 中国移动 | CMI-HKIX | 香港IX专线 | 香港延迟极低 | 优质(香港) |
|
||||
|
||||
上面是中国出境入境的线路段的线路,下面是出境后与国际互联常见的线路
|
||||
|
||||
| 运营商 | 线路代号 | 来源 | 特点 | 线路质量 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 其他 | 国际BGP | HE、NTT、Telia等Tier1 Global | 稳定性取决地区 | 国际互联良好 |
|
||||
| 其他 | 地区IX | HKIX、SGIX、JPIX、Equinix IX | 地区IX,低延迟限区域 | 区域优质 |
|
||||
| 其他 | SoftBank(AS17676) | 日本软银骨干 | 日本方向优化,沿海延迟低 | 沿海优质 |
|
||||
| 其他 | NTT(AS2914) | 日本/全球NTT Communications | 亚洲优化,但有时候不稳定 | 亚洲优质(时不时炸) |
|
||||
| 其他 | PCCW(AS3491) | 香港电讯盈科(Pacnet) | 港区机房常用,对南部优 | 良好至优质(南方) |
|
||||
| 其他 | Singtel(AS7473) | 新加坡电信 | 东南亚方向极优,CN2常中转此线 | 良好至优质(东南亚) |
|
||||
| 其他 | KT(AS4766) | 韩国电信 | 韩国至中国延迟低(但有时候也炸) | 良好(北方) |
|
||||
| 其他 | HGC(AS9304) | 香港和记环球通信 | 香港区域BGP主力 | 一般至良好(香港) |
|
||||
| 其他 | Tata(AS6453) | 印度塔塔通信 | 亚洲跨区骨干,部分IDC混线使用 | 一般(南亚) |
|
||||
| 其他 | Level3(AS3356) | 美国Lumen/Level3 | 北美主干,对CN2转接良好 | 良好(国际) |
|
||||
| 其他 | GTT(AS3257) | 欧洲骨干 | 稳定性高,延迟略高 | 一般(国际) |
|
||||
| 其他 | Telstra(AS1221) | 澳洲电信 | 澳大利亚及东南亚方向优 | 良好(南亚) |
|
||||
|
||||
### **PING值测试**
|
||||
|
||||
依赖项目:[https://github.com/oneclickvirt/pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
|
||||
对于选项1:如果启用中国模式,将仅检测三网全国各省份的PING值延迟,从小到大排序。如果不启用中国模式,默认将不检测三网全国各省份的PING值延迟,仅检测TGDC和主流网站的延迟。
|
||||
|
||||
对于选项6和选项10:默认都进行测试。
|
||||
|
||||
对于中国境内的测试,测试TGDC和主流跨国网站的延迟无意义,所以默认不测试。
|
||||
|
||||
对于参数指定的状态,优先级会高于选项中默认的参数设置。
|
||||
|
||||
所有测不出来失败的地址以及延迟大于等于9999ms的,延迟都设为了9999,延迟超过这个也证明目标延迟过大影响使用,此时认为目标不可用就行。
|
||||
|
||||
### **就近测速**
|
||||
|
||||
依赖项目:[https://github.com/oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest)
|
||||
|
||||
先测的官方推荐的测速点,然后测有代表性的国际测速点,最后测国内三大运营商ping值最低的测速点。
|
||||
|
||||
由于 speedtest.net 和 speedtest.cn 平台公开的测速节点被刷BTPT的刷烂了(他们为了对等上传PCDN的流量狂刷下载),所以这块本人独家融合的境内私有测速节点不再公开,优先使用私有的境内运营商测速节点进行测速,且写死限制每个IP每日仅支持获取测速数据10次,超限自动降级为使用公共测速节点进行测速
|
||||
|
||||
境内使用为主就看境内测速即可,境外使用看境外测速,官方测速点可以代表受测的宿主机本地带宽基准。
|
||||
|
||||
一般来说中国境外的服务器的带宽100Mbps起步,中国境内的服务器1Mbps带宽起步,具体看线路优劣,带宽特别大有时候未必用得上,够用就行了。
|
||||
@@ -423,6 +498,8 @@ Abuser 或 Abuse 的滥用得分会直接影响机器的正常使用(中国境
|
||||
|
||||
## English
|
||||
|
||||
Menu mode is enabled by default, the menu is displayed to select the option test, in the case of menu mode enabled, the default additional CI parameter setting priority is higher than the preset value of the option itself, which is convenient for the user to modify the parameter settings of some single test for a certain option at any time by themselves.
|
||||
|
||||
### Basic System Information
|
||||
|
||||
Dependency project: [https://github.com/oneclickvirt/basics](https://github.com/oneclickvirt/basics) [https://github.com/oneclickvirt/gostun](https://github.com/oneclickvirt/gostun)
|
||||
@@ -591,24 +668,24 @@ If NVMe SSD's 1M (IOPS) value < 1GB/s indicates serious resource overselling.
|
||||
|
||||
Note: This tests real IO, limited to this project. IO tests from other projects don't guarantee universal benchmarks because they may use different parameters, may not set direct IO read/write, may have inconsistent IO engines, or inconsistent test times, all causing benchmark deviations.
|
||||
|
||||
### Streaming Media Unlocking
|
||||
### Platform Unlock Testing
|
||||
|
||||
Dependency project: [https://github.com/oneclickvirt/CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests) [https://github.com/oneclickvirt/UnlockTests](https://github.com/oneclickvirt/UnlockTests)
|
||||
Dependency project: [https://github.com/oneclickvirt/UnlockTests](https://github.com/oneclickvirt/UnlockTests)
|
||||
|
||||
Default only checks cross-border streaming media unlocking.
|
||||
Default only checks cross-border platform unlocking.
|
||||
|
||||
Generally speaking, under normal circumstances, multiple streaming services for one IP should have consistent unlock regions without scattered locations. If multiple platforms show inconsistent unlock regions, the IP likely comes from platforms like IPXO rentals or has been recently announced and used, not yet recognized and corrected by streaming media common databases.
|
||||
Generally speaking, under normal circumstances, multiple platform services for one IP should have consistent unlock regions without scattered locations. If multiple platforms show inconsistent unlock regions, the IP likely comes from platforms like IPXO rentals or has been recently announced and used, not yet recognized and corrected by platform common databases.
|
||||
|
||||
Due to inconsistent IP database recognition speeds across platforms, sometimes some platforms unlock regions normally, some drift to certain router locations, and some drift to where the IP was before you used it.
|
||||
|
||||
| DNS Type | Unlock Method Judgment Necessary | DNS Impact on Unlocking | Description |
|
||||
| -------- | ------------------------------- | ----------------------- | ----------- |
|
||||
| Official Mainstream DNS | No | Small | Streaming unlock mainly relies on node IP, DNS resolution basically doesn't interfere with unlocking |
|
||||
| Non-mainstream / Self-built DNS | Yes | Large | Streaming unlock results greatly affected by DNS resolution, need to judge if it's native unlock or DNS unlock |
|
||||
| Official Mainstream DNS | No | Small | Platform unlock mainly relies on node IP, DNS resolution basically doesn't interfere with unlocking |
|
||||
| Non-mainstream / Self-built DNS | Yes | Large | Platform unlock results greatly affected by DNS resolution, need to judge if it's native unlock or DNS unlock |
|
||||
|
||||
So during testing, if the host currently uses official mainstream DNS, no judgment of whether it's native unlocking will be performed.
|
||||
|
||||
Platforms that are particularly sensitive to IP quality for unlocking include major AI platforms, local streaming services, Reddit, and Spotify. Other multinational platforms are generally less affected by IP quality when it comes to unlocking.
|
||||
Platforms that are particularly sensitive to IP quality for unlocking include major AI platforms, local platform unlocking, Reddit, and Spotify. Other multinational platforms are generally less affected by IP quality when it comes to unlocking.
|
||||
|
||||
### IP Quality Detection
|
||||
|
||||
@@ -616,7 +693,7 @@ Dependency project: [https://github.com/oneclickvirt/securityCheck](https://gith
|
||||
|
||||
Detect IP-related information from 18 databases. Multiple platforms comparing corresponding detection items all show corresponding values, proving that the current IP is indeed as such. Do not only trust information from a single database source:
|
||||
|
||||
[ipinfo.io](https://ipinfo.io) [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)
|
||||
[ipinfo.io](https://ipinfo.io) [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) [cloudflare.com](https://www.cloudflare.com/)
|
||||
|
||||
The following are the meanings corresponding to each field
|
||||
|
||||
@@ -634,6 +711,7 @@ The following are the meanings corresponding to each field
|
||||
| | ASN Abuse Score | Abuse score of the ASN (Autonomous System) to which this IP belongs | Decimal from 0-1, may include risk level notation (Low/Medium/High) | Lower is better |
|
||||
| | Company Abuse Score | Abuse score of the company to which this IP belongs | Decimal from 0-1, may include risk level notation (Low/Medium/High) | Lower is better |
|
||||
| | Threat Level | Threat level classification of IP address | Text descriptions such as low/medium/high/critical | low is best |
|
||||
| | Traffic Proportion | Proportion of humans versus bots within the ASN country of the local IP address | Percentage | Higher human proportion is preferable |
|
||||
| Blacklist Records | Harmless Count | Number of times marked as harmless in various blacklist databases | Non-negative integer | Value itself has no good or bad |
|
||||
| | Malicious Count | Number of times marked as malicious in various blacklist databases | Non-negative integer | Lower is better |
|
||||
| | Suspicious Count | Number of times marked as suspicious in various blacklist databases | Non-negative integer | Lower is better |
|
||||
@@ -643,26 +721,27 @@ The following are the meanings corresponding to each field
|
||||
| | DNS Blacklist-Listed | Number already listed in DNS blacklists | Non-negative integer | Lower is better |
|
||||
| | DNS Blacklist-Other | Number returning other statuses in DNS blacklist checks | Non-negative integer | Value itself has no good or bad |
|
||||
|
||||
When the ASN to which this device's IP belongs has a relatively small number of IP addresses, the traffic proportion can indicate what percentage of your network neighbors constitute genuine traffic. Currently, global internet traffic is roughly 70% human and 30% bot (IPV4). If you need a comparison, use this benchmark to determine whether the ASN of your current IP has reached the internet average usage level within your country. For country-specific benchmark inquiries, you may use https://trafficbenchmark.spiritlhl.net/ to conduct your own searches and comparisons.
|
||||
|
||||
Generally speaking, checking the usage type, company type, and security information below is sufficient. The security score above is only reliable when confirmed by multiple databases, so it's not a problem to skip it. (IDC: generally buy vps identified as this much, is normal in the data center room broadcasting the type of use)
|
||||
|
||||
| Usage Type | Description |
|
||||
| ----------- | ---------- |
|
||||
| hosting | Data center network (IDC) |
|
||||
| residential | Home/Residential network (Home broadband) |
|
||||
| FixedLineISP, ISP | Fixed-line Internet Service Provider (Home broadband) |
|
||||
| isp | Fixed-line Internet Service Provider (Home broadband) |
|
||||
| business | Enterprise office network (Business broadband) |
|
||||
| cellular | Mobile carrier network (Home broadband) |
|
||||
| education | Educational institution network (Education network) |
|
||||
| government | Government institution network (Government network) |
|
||||
| military | Military network (Government network) |
|
||||
| DataCenter/WebHosting/Transit | Data center network (IDC) |
|
||||
| CDN | Content Delivery Network (IDC) |
|
||||
|
||||
| Company Type | Description |
|
||||
| ------------ | ------------ |
|
||||
| business | Business company (Business broadband) |
|
||||
| hosting | Hosting/Data center company (IDC) |
|
||||
| FixedLineISP, ISP | Fixed-line Internet Service Provider (Home broadband) |
|
||||
| business | Business company (Business broadband) |
|
||||
| isp | Fixed-line Internet Service Provider (Home broadband) |
|
||||
| education | Educational institution (Education network) |
|
||||
| government | Government institution (Government network) |
|
||||
|
||||
@@ -670,6 +749,9 @@ The above type descriptions represent the original query information types. Duri
|
||||
|
||||
| Field Category | Field Name | Field Description | Possible Values | Scoring Rules |
|
||||
|---------|---------|---------|---------|---------|
|
||||
| BrowserType | Mainstream Browser | Proportion of ASN Country for Local IP | Percentage | Higher mainstream proportion preferable |
|
||||
| DeviceType | Desktop or Mobile Device | Percentage of ASN Country for Local IP | Percentage | Higher proportion of desktop/mobile devices is preferable |
|
||||
| OSType | Mainstream Operating System | Percentage of ASN Country for Local IP | Percentage | Higher proportion of mainstream systems is preferable |
|
||||
| Cloud Provider | Is Cloud Provider | Whether this IP belongs to a cloud service provider | Yes/No | No good or bad, identification only |
|
||||
| Data Center | Is Data Center | Whether this IP is located in a data center | Yes/No | No is best if concerned about unblocking |
|
||||
| Mobile | Is Mobile | Whether this IP is from a mobile device network | Yes/No | Yes is best if concerned about unblocking |
|
||||
@@ -695,11 +777,11 @@ If Abuse records exist and the score is high, it indicates that the IP may have
|
||||
- Launched large-scale flood attacks
|
||||
- Conducted port scanning or network-wide scanning
|
||||
|
||||
Such historical records will be reported and entered into the Abuse database. If the IP you take over has just been abused by others, delayed Abuse warning emails may still be sent to the service provider. The service provider may misjudge you as the person engaging in malicious behavior, and then terminate the machine, and in most cases, no refund will be given. For cross-border streaming services, Abuse scores may also affect the platform's reputation rating for that IP.
|
||||
Such historical records will be reported and entered into the Abuse database. If the IP you take over has just been abused by others, delayed Abuse warning emails may still be sent to the service provider. The service provider may misjudge you as the person engaging in malicious behavior, and then terminate the machine, and in most cases, no refund will be given. For cross-border platform services, Abuse scores may also affect the platform's reputation rating for that IP.
|
||||
|
||||
For users who need residential broadband for streaming unlock requirements (such as e-commerce needs), attention should be paid to whether "Usage Type" and "Company Type" are both identified as ISP. If it is only single ISP or identified as non-ISP, after subsequent database updates, the IP type is likely to be corrected to Hosting, thereby affecting unlock effectiveness.
|
||||
For users who need residential broadband for platform unlock requirements (such as e-commerce needs), attention should be paid to whether "Usage Type" and "Company Type" are both identified as ISP. If it is only single ISP or identified as non-ISP, after subsequent database updates, the IP type is likely to be corrected to Hosting, thereby affecting unlock effectiveness.
|
||||
|
||||
Most IP identification databases are updated monthly. After updates, IP attributes may be modified, resulting in situations where ISP → Hosting occurs. For some sensitive platforms, such as streaming services in certain specific countries (like Netflix, Spotify), or streaming services that treat different countries differently (like TikTok), the possibility of non-residential unlock is low but not impossible. If you need stable unlock and pursue its special function unlock, you only need to pursue residential broadband streaming unlock. If you're just browsing and watching, there's often no need to pursue residential broadband.
|
||||
Most IP identification databases are updated monthly. After updates, IP attributes may be modified, resulting in situations where ISP → Hosting occurs. For some sensitive platforms, such as platform services in certain specific countries (like Netflix, Spotify), or platform services that treat different countries differently (like TikTok), the possibility of non-residential unlock is low but not impossible. If you need stable unlock and pursue its special function unlock, you only need to pursue residential broadband platform unlock. If you're just browsing and watching, there's often no need to pursue residential broadband.
|
||||
|
||||
It is necessary to elaborate on IP type classification
|
||||
|
||||
@@ -733,6 +815,14 @@ Dependency project: [https://github.com/oneclickvirt/portchecker](https://github
|
||||
|
||||
If the current host doesn't function as a mail server and doesn't send/receive emails, this project indicator can be ignored.
|
||||
|
||||
### PING Testing
|
||||
|
||||
Dependency project: [https://github.com/oneclickvirt/pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
|
||||
Measure the latency from the current IP address to each TG data center and major websites.
|
||||
|
||||
All addresses that cannot be tested for failure, as well as those with latency greater than or equal to 9999ms, have their latency set to 9999. Latency exceeding this threshold also indicates excessive target latency that impairs usability. At this point, the target should be considered unavailable.
|
||||
|
||||
### Nearby Speed Testing
|
||||
|
||||
Dependency project: [https://github.com/oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest)
|
||||
@@ -747,6 +837,8 @@ In daily use, I prefer to use servers with 1Gbps bandwidth, at least the speed o
|
||||
|
||||
## 日本語
|
||||
|
||||
メニューモードはデフォルトで有効化されており、実行時にメニューを表示してオプションテストを選択できます。メニューモードが有効な場合、デフォルトで追加提供されるCIパラメータ設定はオプション自体のプリセット値よりも優先度が高く、ユーザーが特定のオプションに対して随時個別のテストパラメータ設定を変更できるようにします。
|
||||
|
||||
### システム基本情報
|
||||
|
||||
依存プロジェクト:[https://github.com/oneclickvirt/basics](https://github.com/oneclickvirt/basics) [https://github.com/oneclickvirt/gostun](https://github.com/oneclickvirt/gostun)
|
||||
@@ -915,24 +1007,24 @@ NVMe SSDの1M (IOPS)値 < 1GB/s の場合、深刻なリソースオーバーセ
|
||||
|
||||
注意:ここでテストするのは真のIOで、本プロジェクト限定です。本プロジェクト以外でテストしたIOは基準の汎用性を保証しません。彼らがテスト時に同じパラメータを使用していない可能性、IO直接読み書きを設定していない可能性、IOエンジン設定が一致しない可能性、テスト時間設定が一致しない可能性があり、すべて基準の偏差を引き起こします。
|
||||
|
||||
### ストリーミングメディアロック解除
|
||||
### プラットフォームロック解除検出
|
||||
|
||||
依存プロジェクト:[https://github.com/oneclickvirt/CommonMediaTests](https://github.com/oneclickvirt/CommonMediaTests) [https://github.com/lmc999/RegionRestrictionCheck](https://github.com/lmc999/RegionRestrictionCheck)
|
||||
依存プロジェクト:[https://github.com/oneclickvirt/UnlockTests](https://github.com/oneclickvirt/UnlockTests)
|
||||
|
||||
デフォルトでは国境を越えるストリーミングメディアのロック解除のみをチェックします。
|
||||
デフォルトでは国境を越えるプラットフォームのロック解除のみをチェックします。
|
||||
|
||||
一般的に、正常な状況下では、一つのIPの複数のストリーミングメディアのロック解除地域はすべて一致し、あちこち飛び回ることはありません。複数のプラットフォームでロック解除地域が一致しない場合、IPはIPXOなどのプラットフォームからのレンタルか、最近宣告され使用されたもので、ストリーミングメディアの一般的なデータベースに認識修正されていない可能性が高いです。
|
||||
一般的に、正常な状況下では、一つのIPの複数のプラットフォームのロック解除地域はすべて一致し、あちこち飛び回ることはありません。複数のプラットフォームでロック解除地域が一致しない場合、IPはIPXOなどのプラットフォームからのレンタルか、最近宣告され使用されたもので、プラットフォームの一般的なデータベースに認識修正されていない可能性が高いです。
|
||||
|
||||
各プラットフォームのIPデータベース認識速度が一致しないため、時々あるプラットフォームではロック解除地域が正常、あるプラットフォームではルート上のある位置に飛ぶ、あるプラットフォームではIPがあなたによって使用される前にいた位置に飛ぶことがあります。
|
||||
|
||||
| DNS タイプ | ロック解除方式判断の必要性 | DNSのロック解除への影響 | 説明 |
|
||||
| --------- | ------------------------- | ---------------------- | ---- |
|
||||
| 公式主流DNS | 不要 | 小 | ストリーミングメディアのロック解除は主にノードIPに依存し、DNS解析は基本的にロック解除を干渉しない |
|
||||
| 非主流/自建DNS | 必要 | 大 | ストリーミングメディアのロック解除結果はDNS解析の影響を大きく受け、ネイティブロック解除かDNSロック解除かを判断する必要がある |
|
||||
| 公式主流DNS | 不要 | 小 | プラットフォームロック解除は主にノードIPに依存し、DNS解析は基本的にロック解除を干渉しない |
|
||||
| 非主流/自建DNS | 必要 | 大 | プラットフォームロック解除結果はDNS解析の影響を大きく受け、ネイティブロック解除かDNSロック解除かを判断する必要がある |
|
||||
|
||||
そのため、テスト過程で、ホストが現在使用しているのが公式主流のDNSの場合、ネイティブロック解除かどうかの判断は行われません。
|
||||
|
||||
IP品質によるアクセス制限に敏感なのは、実際には主要なAIプラットフォームやローカルストリーミングサービス、redditやspotifyなどであり、その他の多国籍プラットフォームは一般的にIP品質の影響を受けにくい。
|
||||
IP品質によるアクセス制限に敏感なのは、実際には主要なAIプラットフォームやローカルプラットフォームロック解除、redditやspotifyなどであり、その他の多国籍プラットフォームは一般的にIP品質の影響を受けにくい。
|
||||
|
||||
### IP品質検出
|
||||
|
||||
@@ -940,7 +1032,7 @@ IP品質によるアクセス制限に敏感なのは、実際には主要なAI
|
||||
|
||||
18個のデータベースのIP関連情報を検出し、複数のプラットフォームで対応する検出項目がすべて対応する値である場合、現在のIPが確かにそうであることを証明します。1つのデータベースソースの情報のみを信じないでください:
|
||||
|
||||
[ipinfo.io](https://ipinfo.io) [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)
|
||||
[ipinfo.io](https://ipinfo.io) [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) [cloudflare.com](https://www.cloudflare.com/)
|
||||
|
||||
以下は各フィールドの対応する意味です
|
||||
|
||||
@@ -958,6 +1050,7 @@ IP品質によるアクセス制限に敏感なのは、実際には主要なAI
|
||||
| | ASN不正使用スコア | このIPが属するASN(自律システム)の不正使用スコア | 0-1の小数、リスクレベル表記付き(Low/Medium/High)の場合あり | 低いほど良い |
|
||||
| | 企業不正使用スコア | このIPが属する企業の不正使用スコア | 0-1の小数、リスクレベル表記付き(Low/Medium/High)の場合あり | 低いほど良い |
|
||||
| | 脅威レベル(Threat Level) | IPアドレスの脅威レベル分類 | low/medium/high/criticalなどのテキスト記述 | lowが最良 |
|
||||
| | トラフィック比率 | 本機IPのASN所在国における人間とボットの比率 | パーセンテージ | 人間比率が高いほど良い |
|
||||
| ブラックリスト記録 | 無害記録数(Harmless) | 各ブラックリストデータベースで無害とマークされた回数 | 非負整数 | 数値自体に良し悪しなし |
|
||||
| | 悪意記録数(Malicious) | 各ブラックリストデータベースで悪意があるとマークされた回数 | 非負整数 | 低いほど良い |
|
||||
| | 疑わしい記録数(Suspicious) | 各ブラックリストデータベースで疑わしいとマークされた回数 | 非負整数 | 低いほど良い |
|
||||
@@ -967,26 +1060,27 @@ IP品質によるアクセス制限に敏感なのは、実際には主要なAI
|
||||
| | DNSブラックリスト-掲載済み | DNSブラックリストに既に掲載されている数 | 非負整数 | 低いほど良い |
|
||||
| | DNSブラックリスト-その他 | DNSブラックリストチェックで他のステータスを返した数 | 非負整数 | 数値自体に良し悪しなし |
|
||||
|
||||
本機のIPが属するASNが保有するIP数が少ない場合、トラフィック比率からネットワーク内の実際のトラフィック割合を把握できます。現在の全世界インターネットトラフィックの割合は約70%が人間、30%がボットです(IPV4)。比較が必要な場合はこの基準値を基に照らし合わせれば、当該IPのASNが自国内でインターネット平均水準に達しているか判断できます。 特定の国の基準を照会する必要がある場合は、https://trafficbenchmark.spiritlhl.net/ で自ら検索し比較することができます。
|
||||
|
||||
一般的に以下の使用タイプ、会社タイプ、そしてセキュリティ情報の判別で十分です。上記のセキュリティスコアは複数のデータベースで一致が確認された場合のみ信頼できるため、見なくても特に問題ありません。(IDC: サーバーを購入する際、一般的にこの識別されることが多い。データセンターのサーバールームでブロードキャストに使用される通常のタイプである)
|
||||
|
||||
| 使用タイプ | 説明 |
|
||||
| ----------- | ---------- |
|
||||
| hosting | データセンターネットワーク(IDC) |
|
||||
| residential | 家庭/住宅ネットワーク(家庭用回線) |
|
||||
| FixedLineISP、ISP | 固定回線インターネットサービスプロバイダー(家庭用回線) |
|
||||
| isp | 固定回線インターネットサービスプロバイダー(家庭用回線) |
|
||||
| business | 企業オフィスネットワーク(ビジネス回線) |
|
||||
| cellular | モバイル通信事業者ネットワーク(家庭用回線) |
|
||||
| education | 教育機関ネットワーク(教育ネットワーク) |
|
||||
| government | 政府機関ネットワーク(政府ネットワーク) |
|
||||
| military | 軍事ネットワーク(政府ネットワーク) |
|
||||
| DataCenter/WebHosting/Transit | データセンターネットワーク(IDC) |
|
||||
| CDN | コンテンツ配信ネットワーク(IDC) |
|
||||
|
||||
| 会社タイプ | 説明 |
|
||||
| ------------ | ------------ |
|
||||
| business | 企業会社(ビジネス回線) |
|
||||
| hosting | ホスト/データセンター会社(IDC) |
|
||||
| FixedLineISP、ISP | 固定回線インターネットサービスプロバイダー(家庭用回線) |
|
||||
| business | 企業会社(ビジネス回線) |
|
||||
| isp | 固定回線インターネットサービスプロバイダー(家庭用回線) |
|
||||
| education | 教育機関(教育ネットワーク) |
|
||||
| government | 政府機関(政府ネットワーク) |
|
||||
|
||||
@@ -994,6 +1088,9 @@ IP品質によるアクセス制限に敏感なのは、実際には主要なAI
|
||||
|
||||
| フィールドカテゴリ | フィールド名 | フィールド説明 | 可能な値 | 評価ルール |
|
||||
|---------|---------|---------|---------|---------|
|
||||
| ブラウザタイプ | 主流ブラウザかどうか | 本機IPのASN所在国の割合 | パーセンテージ | 主流であるほど良い |
|
||||
| デバイスタイプ | デスクトップ/モバイルデバイスか | 本機IPのASN所在国の割合 | パーセンテージ | デスクトップとモバイルデバイスの割合が高いほど良い |
|
||||
| オペレーティングシステムタイプ | 主流OSか | 本機IPのASN所在国の割合 | パーセンテージ | 主流OSが多いほど良い |
|
||||
| クラウドプロバイダー | クラウドプロバイダーかどうか(Cloud Provider) | このIPがクラウドサービスプロバイダーに属しているか | Yes/No | 良し悪しはなく、識別のみ |
|
||||
| データセンター | データセンターかどうか(Data Center) | このIPがデータセンターに位置しているか | Yes/No | アンブロックを重視する場合はNoが最適 |
|
||||
| モバイルデバイス | モバイルデバイスかどうか(Mobile) | このIPがモバイルデバイスネットワークからのものか | Yes/No | アンブロックを重視する場合はYesが最適 |
|
||||
@@ -1019,11 +1116,11 @@ Abuse記録が存在し、スコアが高い場合、そのIPが過去に以下
|
||||
- 大規模なフラッド攻撃を開始した
|
||||
- ポートスキャンまたはネットワーク全体のスキャンを実施した
|
||||
|
||||
このような履歴記録は報告され、Abuseデータベースに登録される。引き継いだIPが他人によって悪用されたばかりの場合、遅延したAbuse警告メールがサービスプロバイダに送信される可能性がある。サービスプロバイダは、あなた本人が悪意のある行為を行っていると誤判定し、マシンを解約する可能性があり、ほとんどの場合、返金は行われない。国境を越えたストリーミングサービスの場合、AbuseスコアはそのIPに対するプラットフォームの信頼評価にも影響を与える可能性がある。
|
||||
このような履歴記録は報告され、Abuseデータベースに登録される。引き継いだIPが他人によって悪用されたばかりの場合、遅延したAbuse警告メールがサービスプロバイダに送信される可能性がある。サービスプロバイダは、あなた本人が悪意のある行為を行っていると誤判定し、マシンを解約する可能性があり、ほとんどの場合、返金は行われない。国境を越えたプラットフォームサービスの場合、AbuseスコアはそのIPに対するプラットフォームの信頼評価にも影響を与える可能性がある。
|
||||
|
||||
ストリーミング解除要件のために住宅ブロードバンドが必要なユーザー(Eコマース需要など)は、「使用タイプ」と「会社タイプ」の両方がISPとして識別されているかどうかに注意を払う必要がある。単一ISPのみ、または非ISPとして識別されている場合、その後のデータベース更新後、IPタイプがHostingに修正される可能性が高く、解除効果に影響を与える。
|
||||
プラットフォームロック解除要件のために住宅ブロードバンドが必要なユーザー(Eコマース需要など)は、「使用タイプ」と「会社タイプ」の両方がISPとして識別されているかどうかに注意を払う必要がある。単一ISPのみ、または非ISPとして識別されている場合、その後のデータベース更新後、IPタイプがHostingに修正される可能性が高く、解除効果に影響を与える。
|
||||
|
||||
ほとんどのIP識別データベースは月次で更新される。更新後、IP属性が変更され、ISP → Hostingという状況が発生する可能性がある。特定の国のストリーミングサービス(NetflixやSpotifyなど)や、異なる国を区別して扱うストリーミングサービス(TikTokなど)など、一部の敏感なプラットフォームでは、非住宅での解除の可能性は低いが、不可能ではない。安定した解除が必要で、その特別な機能解除を追求する場合にのみ、住宅ブロードバンドストリーミング解除を追求する必要がある。単にブラウジングや視聴するだけであれば、多くの場合、住宅ブロードバンドを追求する必要はない。
|
||||
ほとんどのIP識別データベースは月次で更新される。更新後、IP属性が変更され、ISP → Hostingという状況が発生する可能性がある。特定の国のプラットフォームサービス(NetflixやSpotifyなど)や、異なる国を区別して扱うプラットフォームサービス(TikTokなど)など、一部の敏感なプラットフォームでは、非住宅での解除の可能性は低いが、不可能ではない。安定した解除が必要で、その特別な機能解除を追求する場合にのみ、住宅ブロードバンドプラットフォームロック解除を追求する必要がある。単にブラウジングや視聴するだけであれば、多くの場合、住宅ブロードバンドを追求する必要はない。
|
||||
|
||||
IPタイプの分類について詳しく説明する必要がある
|
||||
|
||||
@@ -1057,6 +1154,14 @@ IPタイプの分類について詳しく説明する必要がある
|
||||
|
||||
現在のホストがメール局として機能せず、電子メールの送受信を行わない場合、この項目指標は無視して構いません。
|
||||
|
||||
### PING検出
|
||||
|
||||
依存プロジェクト:[https://github.com/oneclickvirt/pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
|
||||
現在のIPアドレスからTGの各データセンターおよび主要ウェブサイトまでの遅延を測定します。
|
||||
|
||||
検出不能な失敗アドレスおよび遅延が9999ms以上のものは、遅延を9999に設定する。この値を超える遅延は対象の遅延が過大で利用に影響することを示すため、この時点で対象は利用不可と判断すればよい。
|
||||
|
||||
### 近隣スピードテスト
|
||||
|
||||
依存プロジェクト:[https://github.com/oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest)
|
||||
|
||||
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" // 新加坡
|
||||
)
|
||||
266
api/config.go
Normal file
266
api/config.go
Normal file
@@ -0,0 +1,266 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// WithAnalyzeResult 设置是否启用测试后结果总结分析
|
||||
func WithAnalyzeResult(enable bool) ConfigOption {
|
||||
return func(c *Config) {
|
||||
c.AnalyzeResult = 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)
|
||||
}
|
||||
190
api/runner.go
Normal file
190
api/runner.go
Normal file
@@ -0,0 +1,190 @@
|
||||
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)
|
||||
}
|
||||
if config.AnalyzeResult {
|
||||
output = runner.AppendAnalysisSummary(config, output, tempOutput, &outputMutex)
|
||||
}
|
||||
|
||||
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,11 +0,0 @@
|
||||
package cputest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
_, res := CpuTest("zh", "sysbench", "1")
|
||||
fmt.Print(res)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package disktest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDiskIoTest(t *testing.T) {
|
||||
_, res := DiskTest("zh", "sysbench", "", false, false)
|
||||
fmt.Print(res)
|
||||
}
|
||||
82
go.mod
82
go.mod
@@ -1,23 +1,25 @@
|
||||
module github.com/oneclickvirt/ecs
|
||||
|
||||
go 1.24.5
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
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/CommonMediaTests v0.0.4-20250329123841
|
||||
github.com/oneclickvirt/UnlockTests v0.0.29-20251030094944
|
||||
github.com/oneclickvirt/backtrace v0.0.7-20250811023541
|
||||
github.com/oneclickvirt/basics v0.0.16-20251030093657
|
||||
github.com/oneclickvirt/cputest v0.0.12-20250720122317
|
||||
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.10-20250924030424
|
||||
github.com/oneclickvirt/gostun v0.0.5-20250727155022
|
||||
github.com/oneclickvirt/memorytest v0.0.10-20250924154648
|
||||
github.com/oneclickvirt/nt3 v0.0.8-20250811123903
|
||||
github.com/oneclickvirt/pingtest v0.0.8-20250728015259
|
||||
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/security v0.0.7-20251030094114
|
||||
github.com/oneclickvirt/speedtest v0.0.10-20250728015734
|
||||
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 (
|
||||
@@ -25,11 +27,17 @@ require (
|
||||
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/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.2.6 // 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
|
||||
@@ -42,22 +50,27 @@ require (
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 // 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/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/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/nxtrace/NTrace-core v1.4.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
|
||||
@@ -73,39 +86,40 @@ require (
|
||||
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.53.0 // 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.3.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.14.4 // 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/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/showwin/speedtest-go v1.7.10 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.7 // indirect
|
||||
github.com/spf13/viper v1.20.1 // 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.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // 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.5.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/term v0.33.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.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
|
||||
)
|
||||
|
||||
178
go.sum
178
go.sum
@@ -6,6 +6,22 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo
|
||||
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/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=
|
||||
@@ -14,6 +30,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
@@ -21,8 +39,9 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
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 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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=
|
||||
@@ -56,9 +75,10 @@ github.com/jaypipes/ghw v0.17.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51M
|
||||
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/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
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=
|
||||
@@ -71,14 +91,16 @@ 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/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-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=
|
||||
@@ -92,18 +114,22 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
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/nxtrace/NTrace-core v1.4.2 h1:dSRP18Bn3VGf5CZBzKt8gQWW9mDkq62Np9TCF9RAtp0=
|
||||
github.com/nxtrace/NTrace-core v1.4.2/go.mod h1:wIDOlccuYzY3wBqU89pv2KGHT41i3JA0eRqJU/x9eX4=
|
||||
github.com/oneclickvirt/CommonMediaTests v0.0.4-20250329123841 h1:Zef93z9UiZQwRAKnnZYALmpBKvvuVaq34MEsuWwk6nc=
|
||||
github.com/oneclickvirt/CommonMediaTests v0.0.4-20250329123841/go.mod h1:DAmFPRjFV5p9fEzUUSml5jJGn2f1NZJQCzTxITHDjc4=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.29-20251030094944 h1:c81MmwD3yO/7kkKN+j88VfBuRQcr3zKp0wGTu1zjUug=
|
||||
github.com/oneclickvirt/UnlockTests v0.0.29-20251030094944/go.mod h1:oOa6wj/qECtRMxwBO6D7o0L0F0Q/5sQ747OCnFQqoGE=
|
||||
github.com/oneclickvirt/backtrace v0.0.7-20250811023541 h1:GzkzvUC6U9b6Dkz/Bl4JRPeQ7XBGoW7Qw1aWqzhF+MQ=
|
||||
github.com/oneclickvirt/backtrace v0.0.7-20250811023541/go.mod h1:/+KUtOWz48TyiTTbhVTsp3D6b5WY+4pCgvFBYtUGtns=
|
||||
github.com/oneclickvirt/basics v0.0.16-20251030093657 h1:6SWWILNjJfMTXbspqYRpktUEOe/QIVhGonKO8ODC7n4=
|
||||
github.com/oneclickvirt/basics v0.0.16-20251030093657/go.mod h1:2PV+1ge01zb0Sqzj2V2I7P0wAdFSLF1XgAiumchJJbg=
|
||||
github.com/oneclickvirt/cputest v0.0.12-20250720122317 h1:toiwAK1hZE5b8klu2mOQ7J4sv5yV9lpPKwgPahfRYBQ=
|
||||
github.com/oneclickvirt/cputest v0.0.12-20250720122317/go.mod h1:vjlH8tkPFft1tlLOpeNskXVvurxkHaJ3+dgFxQGLXY4=
|
||||
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=
|
||||
@@ -116,18 +142,20 @@ github.com/oneclickvirt/gostun v0.0.5-20250727155022 h1:/e3gSUrOp1tg/1NTRx+P8B51
|
||||
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-20250924154648 h1:trk6oZ7xs1eVtr+6oIv5IX8LDVtEMG+E6GVzQ810BtU=
|
||||
github.com/oneclickvirt/memorytest v0.0.10-20250924154648/go.mod h1:4kiHsEWkW9r3/1ZcV5xIweU0smiKP0IRfQj74AUIiVI=
|
||||
github.com/oneclickvirt/nt3 v0.0.8-20250811123903 h1:ubSPLh/DSrXj+tOgmRABgi2vrVmbmjjSne+NrVFNmNc=
|
||||
github.com/oneclickvirt/nt3 v0.0.8-20250811123903/go.mod h1:F1v+6xInBKnbUa8gV1M40R1HOzxg+obtduNhx3CTnmA=
|
||||
github.com/oneclickvirt/pingtest v0.0.8-20250728015259 h1:egoxZRZBOWN3JqBwqEsULDyRo2/dpGMeWcmV3U87zig=
|
||||
github.com/oneclickvirt/pingtest v0.0.8-20250728015259/go.mod h1:gxwsxxwitNQiGq2OI0ZogYoOLwc8DtuOdSRe6/EvRqs=
|
||||
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/security v0.0.7-20251030094114 h1:Ax8J1TYqprXyiWNAIJJ3xhoyGhvBlKw4m9j6va5Q2nM=
|
||||
github.com/oneclickvirt/security v0.0.7-20251030094114/go.mod h1:YfDilPFW22szjdUNgv4VOuSwHnZzsFsdPOfRYiMoc3I=
|
||||
github.com/oneclickvirt/speedtest v0.0.10-20250728015734 h1:HKO7/JQ74ueXA8Wo8NIvcK9DphbEG/YTfAAVz/akSiY=
|
||||
github.com/oneclickvirt/speedtest v0.0.10-20250728015734/go.mod h1:0W8vnMbA3iucXLXFdGfe9Ia6RPS0izRO7jvu/SnH1P8=
|
||||
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=
|
||||
@@ -155,8 +183,8 @@ github.com/prometheus-community/pro-bing v0.4.1 h1:aMaJwyifHZO0y+h8+icUz0xbToHbi
|
||||
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.53.0 h1:QHX46sISpG2S03dPeZBgVIZp8dGagIaiu2FiVYvpCZI=
|
||||
github.com/quic-go/quic-go v0.53.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
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=
|
||||
@@ -164,28 +192,26 @@ 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.3.0 h1:4/3S3SVkHnVZX91EHFvAMV7K42AnJ0XuymRR2C5HlGE=
|
||||
github.com/rodaine/table v1.3.0/go.mod h1:47zRsHar4zw0jgxGxL9YtFfs7EGN6B/TaS+/Dmk4WxU=
|
||||
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.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
|
||||
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
||||
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/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/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/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.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
|
||||
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
||||
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=
|
||||
@@ -196,23 +222,28 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
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/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.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.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
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=
|
||||
@@ -220,25 +251,29 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
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.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
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.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
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=
|
||||
@@ -247,54 +282,55 @@ 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.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
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.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
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.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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
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.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
|
||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
|
||||
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.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
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.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
863
goecs.go
863
goecs.go
@@ -1,177 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/CommonMediaTests/commediatests"
|
||||
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"
|
||||
"github.com/oneclickvirt/ecs/cputest"
|
||||
"github.com/oneclickvirt/ecs/disktest"
|
||||
"github.com/oneclickvirt/ecs/memorytest"
|
||||
"github.com/oneclickvirt/ecs/nexttrace"
|
||||
"github.com/oneclickvirt/ecs/speedtest"
|
||||
"github.com/oneclickvirt/ecs/unlocktest"
|
||||
"github.com/oneclickvirt/ecs/upstreams"
|
||||
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"
|
||||
"github.com/oneclickvirt/pingtest/pt"
|
||||
"github.com/oneclickvirt/portchecker/email"
|
||||
speedtestmodel "github.com/oneclickvirt/speedtest/model"
|
||||
)
|
||||
|
||||
var (
|
||||
ecsVersion = "v0.1.91"
|
||||
menuMode bool
|
||||
onlyChinaTest bool
|
||||
input, choice string
|
||||
showVersion bool
|
||||
enableLogger bool
|
||||
language string
|
||||
cpuTestMethod, cpuTestThreadMode string
|
||||
memoryTestMethod string
|
||||
diskTestMethod, diskTestPath string
|
||||
diskMultiCheck bool
|
||||
nt3CheckType, nt3Location string
|
||||
spNum int
|
||||
width = 82
|
||||
basicStatus, cpuTestStatus, memoryTestStatus, diskTestStatus bool
|
||||
commTestStatus, utTestStatus, securityTestStatus, emailTestStatus bool
|
||||
backtraceStatus, nt3Status, speedTestStatus, pingTestStatus bool
|
||||
autoChangeDiskTestMethod = true
|
||||
filePath = "goecs.txt"
|
||||
enabelUpload = true
|
||||
onlyIpInfoCheckStatus, help bool
|
||||
goecsFlag = flag.NewFlagSet("goecs", flag.ContinueOnError)
|
||||
finish bool
|
||||
ecsVersion = "v0.1.119" // 融合怪版本号
|
||||
configs = params.NewConfig(ecsVersion) // 全局配置实例
|
||||
userSetFlags = make(map[string]bool) // 用于跟踪哪些参数是用户显式设置的
|
||||
)
|
||||
|
||||
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)
|
||||
inputChan := make(chan string, 1)
|
||||
go func() {
|
||||
select {
|
||||
case <-sigChan:
|
||||
fmt.Println("\n程序在选择过程中被用户中断")
|
||||
os.Exit(0)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
for {
|
||||
go func() {
|
||||
var input string
|
||||
fmt.Print("请输入选项 / Please enter your choice: ")
|
||||
fmt.Scanln(&input)
|
||||
input = strings.TrimSpace(input)
|
||||
input = strings.TrimRight(input, "\n")
|
||||
select {
|
||||
case inputChan <- input:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case input := <-inputChan:
|
||||
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")
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parseFlags() {
|
||||
goecsFlag.BoolVar(&help, "h", false, "Show help information")
|
||||
goecsFlag.BoolVar(&showVersion, "v", false, "Display version information")
|
||||
goecsFlag.BoolVar(&menuMode, "menu", true, "Enable/Disable menu mode, disable example: -menu=false") // true 默认启用菜单栏模式
|
||||
goecsFlag.StringVar(&language, "l", "zh", "Set language (supported: en, zh)")
|
||||
goecsFlag.BoolVar(&basicStatus, "basic", true, "Enable/Disable basic test")
|
||||
goecsFlag.BoolVar(&cpuTestStatus, "cpu", true, "Enable/Disable CPU test")
|
||||
goecsFlag.BoolVar(&memoryTestStatus, "memory", true, "Enable/Disable memory test")
|
||||
goecsFlag.BoolVar(&diskTestStatus, "disk", true, "Enable/Disable disk test")
|
||||
goecsFlag.BoolVar(&commTestStatus, "comm", true, "Enable/Disable common media test")
|
||||
goecsFlag.BoolVar(&utTestStatus, "ut", true, "Enable/Disable unlock media test")
|
||||
goecsFlag.BoolVar(&securityTestStatus, "security", true, "Enable/Disable security test")
|
||||
goecsFlag.BoolVar(&emailTestStatus, "email", true, "Enable/Disable email port test")
|
||||
goecsFlag.BoolVar(&backtraceStatus, "backtrace", true, "Enable/Disable backtrace test (in 'en' language or on windows it always false)")
|
||||
goecsFlag.BoolVar(&nt3Status, "nt3", true, "Enable/Disable NT3 test (in 'en' language or on windows it always false)")
|
||||
goecsFlag.BoolVar(&speedTestStatus, "speed", true, "Enable/Disable speed test")
|
||||
goecsFlag.StringVar(&cpuTestMethod, "cpum", "sysbench", "Set CPU test method (supported: sysbench, geekbench, winsat)")
|
||||
goecsFlag.StringVar(&cpuTestThreadMode, "cput", "multi", "Set CPU test thread mode (supported: single, multi)")
|
||||
goecsFlag.StringVar(&memoryTestMethod, "memorym", "stream", "Set memory test method (supported: stream, sysbench, dd, winsat, auto)")
|
||||
goecsFlag.StringVar(&diskTestMethod, "diskm", "fio", "Set disk test method (supported: fio, dd, winsat)")
|
||||
goecsFlag.StringVar(&diskTestPath, "diskp", "", "Set disk test path, e.g., -diskp /root")
|
||||
goecsFlag.BoolVar(&diskMultiCheck, "diskmc", false, "Enable/Disable multiple disk checks, e.g., -diskmc=false")
|
||||
goecsFlag.StringVar(&nt3Location, "nt3loc", "GZ", "Specify NT3 test location (supported: GZ, SH, BJ, CD, ALL for Guangzhou, Shanghai, Beijing, Chengdu and all)")
|
||||
goecsFlag.StringVar(&nt3CheckType, "nt3t", "ipv4", "Set NT3 test type (supported: both, ipv4, ipv6)")
|
||||
goecsFlag.IntVar(&spNum, "spnum", 2, "Set the number of servers per operator for speed test")
|
||||
goecsFlag.BoolVar(&enableLogger, "log", false, "Enable/Disable logging in the current path")
|
||||
goecsFlag.BoolVar(&enabelUpload, "upload", true, "Enable/Disable upload the result")
|
||||
goecsFlag.Parse(os.Args[1:])
|
||||
}
|
||||
|
||||
func handleHelpAndVersion() bool {
|
||||
if help {
|
||||
fmt.Printf("Usage: %s [options]\n", os.Args[0])
|
||||
goecsFlag.PrintDefaults()
|
||||
return true
|
||||
}
|
||||
if showVersion {
|
||||
fmt.Println(ecsVersion)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func initLogger() {
|
||||
if enableLogger {
|
||||
if configs.EnableLogger {
|
||||
gostunmodel.EnableLoger = true
|
||||
basicmodel.EnableLoger = true
|
||||
cputestmodel.EnableLoger = true
|
||||
memorytestmodel.EnableLoger = true
|
||||
disktestmodel.EnableLoger = true
|
||||
commediatests.EnableLoger = true
|
||||
unlocktestmodel.EnableLoger = true
|
||||
ptmodel.EnableLoger = true
|
||||
backtracemodel.EnableLoger = true
|
||||
@@ -180,735 +47,65 @@ func initLogger() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleMenuMode(preCheck utils.NetCheckResult) {
|
||||
basicStatus, cpuTestStatus, memoryTestStatus, diskTestStatus = false, false, false, false
|
||||
commTestStatus, utTestStatus, securityTestStatus, emailTestStatus = false, false, false, false
|
||||
backtraceStatus, nt3Status, speedTestStatus = false, false, false
|
||||
autoChangeDiskTestMethod = true
|
||||
printMenuOptions(preCheck)
|
||||
Loop:
|
||||
for {
|
||||
choice = getMenuChoice(language)
|
||||
switch choice {
|
||||
case "0":
|
||||
os.Exit(0)
|
||||
case "1":
|
||||
setFullTestStatus(preCheck)
|
||||
onlyChinaTest = utils.CheckChina(enableLogger)
|
||||
break Loop
|
||||
case "2":
|
||||
setMinimalTestStatus(preCheck)
|
||||
break Loop
|
||||
case "3":
|
||||
setStandardTestStatus(preCheck)
|
||||
break Loop
|
||||
case "4":
|
||||
setNetworkFocusedTestStatus(preCheck)
|
||||
break Loop
|
||||
case "5":
|
||||
setUnlockFocusedTestStatus(preCheck)
|
||||
break Loop
|
||||
case "6":
|
||||
if !preCheck.Connected {
|
||||
fmt.Println("Can not test without network connection!")
|
||||
return
|
||||
}
|
||||
setNetworkOnlyTestStatus()
|
||||
break Loop
|
||||
case "7":
|
||||
if !preCheck.Connected {
|
||||
fmt.Println("Can not test without network connection!")
|
||||
return
|
||||
}
|
||||
setUnlockOnlyTestStatus()
|
||||
break Loop
|
||||
case "8":
|
||||
setHardwareOnlyTestStatus(preCheck)
|
||||
break Loop
|
||||
case "9":
|
||||
if !preCheck.Connected {
|
||||
fmt.Println("Can not test without network connection!")
|
||||
return
|
||||
}
|
||||
setIPQualityTestStatus()
|
||||
break Loop
|
||||
case "10":
|
||||
if !preCheck.Connected {
|
||||
fmt.Println("Can not test without network connection!")
|
||||
return
|
||||
}
|
||||
nt3Location = "ALL"
|
||||
setRouteTestStatus()
|
||||
break Loop
|
||||
default:
|
||||
printInvalidChoice()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printMenuOptions(preCheck utils.NetCheckResult) {
|
||||
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 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(ecsVersion, githubInfo.TagName)
|
||||
} else {
|
||||
cmp = 0
|
||||
}
|
||||
}
|
||||
switch language {
|
||||
case "zh":
|
||||
fmt.Printf("VPS融合怪版本: %s\n", 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质量检测+上游及三网回程+广州三网回程详细路由+全国延迟+测速节点11个)")
|
||||
fmt.Println("7. 解锁单项(御三家解锁+常用流媒体解锁)")
|
||||
fmt.Println("8. 硬件单项(系统信息+CPU+dd磁盘测试+fio磁盘测试)")
|
||||
fmt.Println("9. IP质量检测(15个数据库的IP质量检测+邮件端口检测)")
|
||||
fmt.Println("10. 三网回程线路检测+三网回程详细路由(北京上海广州成都)+三网延迟测试(全国)")
|
||||
fmt.Println("0. 退出程序")
|
||||
case "en":
|
||||
fmt.Printf("VPS Fusion Monster Test Version: %s\n", 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 + Basic Unlock Tests + 5 Speed Test Nodes)")
|
||||
fmt.Println("4. Network-Focused Test Suite (System Info + CPU + Memory + Disk + 5 Speed Test Nodes)")
|
||||
fmt.Println("5. Unlock-Focused Test Suite (System Info + CPU + Memory + Disk IO + Basic Unlock Tests + Common Streaming Services + 5 Speed Test Nodes)")
|
||||
fmt.Println("6. Network-Only Test (IP Quality Test + 5 Speed Test Nodes)")
|
||||
fmt.Println("7. Unlock-Only Test (Basic Unlock Tests + Common Streaming Services 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")
|
||||
}
|
||||
}
|
||||
|
||||
func setFullTestStatus(preCheck utils.NetCheckResult) {
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
commTestStatus = true
|
||||
utTestStatus = true
|
||||
securityTestStatus = true
|
||||
emailTestStatus = true
|
||||
backtraceStatus = true
|
||||
nt3Status = true
|
||||
speedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
func setMinimalTestStatus(preCheck utils.NetCheckResult) {
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
speedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
func setStandardTestStatus(preCheck utils.NetCheckResult) {
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
utTestStatus = true
|
||||
nt3Status = true
|
||||
speedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
func setNetworkFocusedTestStatus(preCheck utils.NetCheckResult) {
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
backtraceStatus = true
|
||||
nt3Status = true
|
||||
speedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
func setUnlockFocusedTestStatus(preCheck utils.NetCheckResult) {
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
if preCheck.Connected {
|
||||
commTestStatus = true
|
||||
utTestStatus = true
|
||||
speedTestStatus = true
|
||||
}
|
||||
}
|
||||
|
||||
func setNetworkOnlyTestStatus() {
|
||||
onlyIpInfoCheckStatus = true
|
||||
securityTestStatus = true
|
||||
speedTestStatus = true
|
||||
backtraceStatus = true
|
||||
nt3Status = true
|
||||
pingTestStatus = true
|
||||
}
|
||||
|
||||
func setUnlockOnlyTestStatus() {
|
||||
onlyIpInfoCheckStatus = true
|
||||
commTestStatus = true
|
||||
utTestStatus = true
|
||||
}
|
||||
|
||||
func setHardwareOnlyTestStatus(preCheck utils.NetCheckResult) {
|
||||
_ = preCheck
|
||||
basicStatus = true
|
||||
cpuTestStatus = true
|
||||
memoryTestStatus = true
|
||||
diskTestStatus = true
|
||||
securityTestStatus = false
|
||||
autoChangeDiskTestMethod = false
|
||||
}
|
||||
|
||||
func setIPQualityTestStatus() {
|
||||
onlyIpInfoCheckStatus = true
|
||||
securityTestStatus = true
|
||||
emailTestStatus = true
|
||||
}
|
||||
|
||||
func setRouteTestStatus() {
|
||||
onlyIpInfoCheckStatus = true
|
||||
backtraceStatus = true
|
||||
nt3Status = true
|
||||
pingTestStatus = true
|
||||
}
|
||||
|
||||
func printInvalidChoice() {
|
||||
if language == "zh" {
|
||||
fmt.Println("无效的选项")
|
||||
} else {
|
||||
fmt.Println("Invalid choice")
|
||||
}
|
||||
}
|
||||
|
||||
func handleLanguageSpecificSettings() {
|
||||
if language == "en" {
|
||||
backtraceStatus = false
|
||||
nt3Status = false
|
||||
if configs.Language == "en" {
|
||||
configs.BacktraceStatus = false
|
||||
configs.Nt3Status = false
|
||||
}
|
||||
if !enabelUpload {
|
||||
securityTestStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
func handleSignalInterrupt(sig chan os.Signal, startTime *time.Time, output *string, _ string, uploadDone chan bool, outputMutex *sync.Mutex) {
|
||||
select {
|
||||
case <-sig:
|
||||
if !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("", width)
|
||||
if 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("", width)
|
||||
}, "", "")
|
||||
*output += timeInfo
|
||||
finalOutput := *output
|
||||
outputMutex.Unlock()
|
||||
resultChan := make(chan struct {
|
||||
httpURL string
|
||||
httpsURL string
|
||||
}, 1)
|
||||
if enabelUpload {
|
||||
go func() {
|
||||
httpURL, httpsURL := utils.ProcessAndUpload(finalOutput, filePath, enabelUpload)
|
||||
resultChan <- struct {
|
||||
httpURL string
|
||||
httpsURL string
|
||||
}{httpURL, httpsURL}
|
||||
uploadDone <- true
|
||||
}()
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
if result.httpURL != "" || result.httpsURL != "" {
|
||||
if 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 <-time.After(30 * time.Second):
|
||||
if 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)
|
||||
}
|
||||
}
|
||||
|
||||
func runChineseTests(preCheck utils.NetCheckResult, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex) {
|
||||
*output = runBasicTests(preCheck, basicInfo, securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = runCPUTest(*output, tempOutput, outputMutex)
|
||||
*output = runMemoryTest(*output, tempOutput, outputMutex)
|
||||
*output = runDiskTest(*output, tempOutput, outputMutex)
|
||||
if onlyIpInfoCheckStatus && !basicStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = runIpInfoCheck(*output, tempOutput, outputMutex)
|
||||
}
|
||||
if utTestStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" && !onlyChinaTest {
|
||||
wg1.Add(1)
|
||||
go func() {
|
||||
defer wg1.Done()
|
||||
*mediaInfo = unlocktest.MediaTest(language)
|
||||
}()
|
||||
}
|
||||
if emailTestStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
wg2.Add(1)
|
||||
go func() {
|
||||
defer wg2.Done()
|
||||
*emailInfo = email.EmailCheck()
|
||||
}()
|
||||
}
|
||||
if (onlyChinaTest || pingTestStatus) && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
wg3.Add(1)
|
||||
go func() {
|
||||
defer wg3.Done()
|
||||
*ptInfo = pt.PingTest()
|
||||
}()
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = runStreamingTests(wg1, mediaInfo, *output, tempOutput, outputMutex)
|
||||
*output = runSecurityTests(*securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = runEmailTests(wg2, emailInfo, *output, tempOutput, outputMutex)
|
||||
}
|
||||
if runtime.GOOS != "windows" && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = runNetworkTests(wg3, ptInfo, *output, tempOutput, outputMutex)
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = runSpeedTests(*output, tempOutput, outputMutex)
|
||||
}
|
||||
*output = appendTimeInfo(*output, tempOutput, startTime, outputMutex)
|
||||
}
|
||||
|
||||
func runEnglishTests(preCheck utils.NetCheckResult, wg1, wg2 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex) {
|
||||
*output = runBasicTests(preCheck, basicInfo, securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = runCPUTest(*output, tempOutput, outputMutex)
|
||||
*output = runMemoryTest(*output, tempOutput, outputMutex)
|
||||
*output = runDiskTest(*output, tempOutput, outputMutex)
|
||||
if onlyIpInfoCheckStatus && !basicStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
*output = runIpInfoCheck(*output, tempOutput, outputMutex)
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" {
|
||||
if utTestStatus {
|
||||
wg1.Add(1)
|
||||
go func() {
|
||||
defer wg1.Done()
|
||||
*mediaInfo = unlocktest.MediaTest(language)
|
||||
}()
|
||||
}
|
||||
if emailTestStatus {
|
||||
wg2.Add(1)
|
||||
go func() {
|
||||
defer wg2.Done()
|
||||
*emailInfo = email.EmailCheck()
|
||||
}()
|
||||
}
|
||||
*output = runStreamingTests(wg1, mediaInfo, *output, tempOutput, outputMutex)
|
||||
*output = runSecurityTests(*securityInfo, *output, tempOutput, outputMutex)
|
||||
*output = runEmailTests(wg2, emailInfo, *output, tempOutput, outputMutex)
|
||||
*output = runEnglishSpeedTests(*output, tempOutput, outputMutex)
|
||||
}
|
||||
*output = appendTimeInfo(*output, tempOutput, startTime, outputMutex)
|
||||
}
|
||||
|
||||
// runIpInfoCheck 系统和网络基础信息检测不进行测试的时候,该函数检测取得本机IP信息并显示(单项测试中输出)
|
||||
func runIpInfoCheck(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
var ipinfo string
|
||||
upstreams.IPV4, upstreams.IPV6, ipinfo = utils.OnlyBasicsIpInfo(language)
|
||||
if ipinfo != "" {
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle("IP信息", width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("IP-Information", width)
|
||||
}
|
||||
fmt.Printf("%s", ipinfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runBasicTests(preCheck utils.NetCheckResult, basicInfo, securityInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
utils.PrintHead(language, width, ecsVersion)
|
||||
if basicStatus || securityTestStatus {
|
||||
if basicStatus {
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle("系统基础信息", width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("System-Basic-Information", width)
|
||||
}
|
||||
}
|
||||
if preCheck.Connected && preCheck.StackType == "DualStack" {
|
||||
upstreams.IPV4, upstreams.IPV6, *basicInfo, *securityInfo, nt3CheckType = utils.BasicsAndSecurityCheck(language, nt3CheckType, securityTestStatus)
|
||||
} else if preCheck.Connected && preCheck.StackType == "IPv4" {
|
||||
upstreams.IPV4, upstreams.IPV6, *basicInfo, *securityInfo, nt3CheckType = utils.BasicsAndSecurityCheck(language, "ipv4", securityTestStatus)
|
||||
} else if preCheck.Connected && preCheck.StackType == "IPv6" {
|
||||
upstreams.IPV4, upstreams.IPV6, *basicInfo, *securityInfo, nt3CheckType = utils.BasicsAndSecurityCheck(language, "ipv6", securityTestStatus)
|
||||
} else {
|
||||
upstreams.IPV4, upstreams.IPV6, *basicInfo, *securityInfo, nt3CheckType = utils.BasicsAndSecurityCheck(language, "", false)
|
||||
securityTestStatus = false
|
||||
}
|
||||
if basicStatus {
|
||||
fmt.Printf("%s", *basicInfo)
|
||||
} else if (input == "6" || input == "9") && securityTestStatus {
|
||||
scanner := bufio.NewScanner(strings.NewReader(*basicInfo))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.Contains(line, "IPV") {
|
||||
fmt.Println(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runCPUTest(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if cpuTestStatus {
|
||||
realTestMethod, res := cputest.CpuTest(language, cpuTestMethod, cpuTestThreadMode)
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU测试-通过%s测试", realTestMethod), width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("CPU-Test--%s-Method", realTestMethod), width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runMemoryTest(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if memoryTestStatus {
|
||||
realTestMethod, res := memorytest.MemoryTest(language, memoryTestMethod)
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("内存测试-通过%s测试", realTestMethod), width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Memory-Test--%s-Method", realTestMethod), width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runDiskTest(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if diskTestStatus && autoChangeDiskTestMethod {
|
||||
realTestMethod, res := disktest.DiskTest(language, diskTestMethod, diskTestPath, diskMultiCheck, autoChangeDiskTestMethod)
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", realTestMethod), width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", realTestMethod), width)
|
||||
}
|
||||
fmt.Print(res)
|
||||
} else if diskTestStatus && !autoChangeDiskTestMethod {
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", "dd"), width)
|
||||
_, res := disktest.DiskTest(language, "dd", diskTestPath, diskMultiCheck, autoChangeDiskTestMethod)
|
||||
fmt.Print(res)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("硬盘测试-通过%s测试", "fio"), width)
|
||||
_, res = disktest.DiskTest(language, "fio", diskTestPath, diskMultiCheck, autoChangeDiskTestMethod)
|
||||
fmt.Print(res)
|
||||
} else {
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", "dd"), width)
|
||||
_, res := disktest.DiskTest(language, "dd", diskTestPath, diskMultiCheck, autoChangeDiskTestMethod)
|
||||
fmt.Print(res)
|
||||
utils.PrintCenteredTitle(fmt.Sprintf("Disk-Test--%s-Method", "fio"), width)
|
||||
_, res = disktest.DiskTest(language, "fio", diskTestPath, diskMultiCheck, autoChangeDiskTestMethod)
|
||||
fmt.Print(res)
|
||||
}
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runStreamingTests(wg1 *sync.WaitGroup, mediaInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if language == "zh" {
|
||||
if commTestStatus && !onlyChinaTest {
|
||||
utils.PrintCenteredTitle("御三家流媒体解锁", width)
|
||||
fmt.Printf("%s", commediatests.MediaTests(language))
|
||||
}
|
||||
}
|
||||
if utTestStatus && (language == "zh" && !onlyChinaTest || language == "en") {
|
||||
wg1.Wait()
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle("跨国流媒体解锁", width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("Cross-Border-Streaming-Media-Unlock", width)
|
||||
}
|
||||
fmt.Printf("%s", *mediaInfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runSecurityTests(securityInfo, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if securityTestStatus {
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle("IP质量检测", width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("IP-Quality-Check", width)
|
||||
}
|
||||
fmt.Printf("%s", securityInfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runEmailTests(wg2 *sync.WaitGroup, emailInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if emailTestStatus {
|
||||
wg2.Wait()
|
||||
if language == "zh" {
|
||||
utils.PrintCenteredTitle("邮件端口检测", width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("Email-Port-Check", width)
|
||||
}
|
||||
fmt.Println(*emailInfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runNetworkTests(wg3 *sync.WaitGroup, ptInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if backtraceStatus && !onlyChinaTest {
|
||||
utils.PrintCenteredTitle("上游及回程线路检测", width)
|
||||
upstreams.UpstreamsCheck() // 不能在重定向的同时外部并发,此处仅可以顺序执行
|
||||
}
|
||||
if nt3Status && !onlyChinaTest {
|
||||
utils.PrintCenteredTitle("三网回程路由检测", width)
|
||||
nexttrace.NextTrace3Check(language, nt3Location, nt3CheckType) // 不能在重定向的同时外部并发,此处仅可以顺序执行
|
||||
}
|
||||
if (onlyChinaTest || pingTestStatus) && *ptInfo != "" {
|
||||
wg3.Wait()
|
||||
utils.PrintCenteredTitle("三网ICMP的PING值检测", width)
|
||||
fmt.Println(*ptInfo)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runSpeedTests(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if speedTestStatus {
|
||||
utils.PrintCenteredTitle("就近节点测速", width)
|
||||
speedtest.ShowHead(language)
|
||||
if choice == "1" || !menuMode {
|
||||
speedtest.NearbySP()
|
||||
speedtest.CustomSP("net", "global", 2, language)
|
||||
speedtest.CustomSP("net", "cu", spNum, language)
|
||||
speedtest.CustomSP("net", "ct", spNum, language)
|
||||
speedtest.CustomSP("net", "cmcc", spNum, language)
|
||||
} else if choice == "2" || choice == "3" || choice == "4" || choice == "5" {
|
||||
speedtest.CustomSP("net", "global", 4, language)
|
||||
} else if choice == "6" {
|
||||
speedtest.CustomSP("net", "global", 11, language)
|
||||
}
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func runEnglishSpeedTests(output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
return utils.PrintAndCapture(func() {
|
||||
if speedTestStatus {
|
||||
utils.PrintCenteredTitle("Speed-Test", width)
|
||||
speedtest.ShowHead(language)
|
||||
speedtest.NearbySP()
|
||||
speedtest.CustomSP("net", "global", -1, language)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func appendTimeInfo(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("", width)
|
||||
if 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("", width)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
func handleUploadResults(output string) {
|
||||
httpURL, httpsURL := utils.ProcessAndUpload(output, filePath, enabelUpload)
|
||||
if httpURL != "" || httpsURL != "" {
|
||||
if 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")
|
||||
}
|
||||
if !configs.EnableUpload {
|
||||
configs.SecurityTestStatus = false
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
parseFlags()
|
||||
if handleHelpAndVersion() {
|
||||
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 menuMode {
|
||||
handleMenuMode(preCheck)
|
||||
if configs.MenuMode {
|
||||
menu.HandleMenuMode(preCheck, configs)
|
||||
} else {
|
||||
onlyIpInfoCheckStatus = true
|
||||
configs.OnlyIpInfoCheck = true
|
||||
}
|
||||
handleLanguageSpecificSettings()
|
||||
if !preCheck.Connected {
|
||||
enabelUpload = false
|
||||
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 handleSignalInterrupt(sig, &startTime, &output, tempOutput, uploadDone, &outputMutex)
|
||||
switch language {
|
||||
go runner.HandleSignalInterrupt(sig, configs, &startTime, &output, tempOutput, uploadDone, &outputMutex)
|
||||
switch configs.Language {
|
||||
case "zh":
|
||||
runChineseTests(preCheck, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex)
|
||||
runner.RunChineseTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex, &infoMutex)
|
||||
case "en":
|
||||
runEnglishTests(preCheck, &wg1, &wg2, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &output, tempOutput, startTime, &outputMutex)
|
||||
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 {
|
||||
handleUploadResults(output)
|
||||
if configs.AnalyzeResult {
|
||||
output = runner.AppendAnalysisSummary(configs, output, tempOutput, &outputMutex)
|
||||
}
|
||||
finish = true
|
||||
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()
|
||||
|
||||
47
goecs.sh
47
goecs.sh
@@ -66,28 +66,28 @@ download_file() {
|
||||
}
|
||||
|
||||
check_china() {
|
||||
_yellow "正在检测IP所在区域......"
|
||||
_yellow "Detecting IP region......"
|
||||
if [ -z "${CN}" ]; then
|
||||
if curl -m 6 -s https://ipapi.co/json | grep -q 'China'; then
|
||||
_yellow "根据ipapi.co提供的信息,当前IP可能在中国"
|
||||
_yellow "According to ipapi.co, this IP may be located in China"
|
||||
if [ "$noninteractive" != "true" ]; then
|
||||
reading "是否使用中国镜像完成安装? ([y]/n) " input
|
||||
reading "Use China mirror for installation? ([y]/n) " input
|
||||
case $input in
|
||||
[yY][eE][sS] | [yY] | "")
|
||||
_green "已选择使用中国镜像"
|
||||
_green "China mirror selected"
|
||||
CN=true
|
||||
;;
|
||||
[nN][oO] | [nN])
|
||||
_yellow "已选择不使用中国镜像"
|
||||
_yellow "China mirror not selected"
|
||||
CN=false
|
||||
;;
|
||||
*)
|
||||
_green "已选择使用中国镜像"
|
||||
_green "China mirror selected"
|
||||
CN=true
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# 在非交互模式下默认不使用中国镜像
|
||||
# In non-interactive mode, default to not using China mirror
|
||||
CN=false
|
||||
fi
|
||||
else
|
||||
@@ -152,7 +152,7 @@ goecs_check() {
|
||||
os=$(uname -s 2>/dev/null || echo "Unknown")
|
||||
arch=$(uname -m 2>/dev/null || echo "Unknown")
|
||||
check_china
|
||||
ECS_VERSION="0.1.90"
|
||||
ECS_VERSION="0.1.118"
|
||||
for api in \
|
||||
"https://api.github.com/repos/oneclickvirt/ecs/releases/latest" \
|
||||
"https://githubapi.spiritlhl.workers.dev/repos/oneclickvirt/ecs/releases/latest" \
|
||||
@@ -164,8 +164,8 @@ goecs_check() {
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$ECS_VERSION" ]; then
|
||||
_yellow "Unable to get version info, using default version 0.1.90"
|
||||
ECS_VERSION="0.1.90"
|
||||
_yellow "Unable to get version info, using default version 0.1.118"
|
||||
ECS_VERSION="0.1.118"
|
||||
fi
|
||||
version_output=""
|
||||
for cmd_path in "goecs" "./goecs" "/usr/bin/goecs" "/usr/local/bin/goecs"; do
|
||||
@@ -260,29 +260,36 @@ goecs_check() {
|
||||
fi
|
||||
rm -f goecs.zip README.md LICENSE README_EN.md
|
||||
chmod 777 goecs
|
||||
installed_to_system=false
|
||||
for install_path in "/usr/bin" "/usr/local/bin"; do
|
||||
if [ -d "$install_path" ]; then
|
||||
cp -f goecs "$install_path/"
|
||||
break
|
||||
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; then
|
||||
sed -i "s/^$PARAM.*/$PARAM = $NEW_VALUE/" /etc/sysctl.conf
|
||||
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
|
||||
echo "$PARAM = $NEW_VALUE" >> /etc/sysctl.conf 2>/dev/null || true
|
||||
fi
|
||||
sysctl -p >/dev/null 2>&1
|
||||
sysctl -p >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
setcap cap_net_raw=+ep goecs 2>/dev/null
|
||||
setcap cap_net_raw=+ep /usr/bin/goecs 2>/dev/null
|
||||
setcap cap_net_raw=+ep /usr/local/bin/goecs 2>/dev/null
|
||||
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 || ./goecs -v
|
||||
goecs -v 2>/dev/null || ./goecs -v
|
||||
}
|
||||
|
||||
InstallSysbench() {
|
||||
|
||||
652
internal/analysis/summary.go
Normal file
652
internal/analysis/summary.go
Normal file
@@ -0,0 +1,652 @@
|
||||
package analysis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
)
|
||||
|
||||
var (
|
||||
mbpsRe = regexp.MustCompile(`(?i)(\d+(?:\.\d+)?)\s*mbps`)
|
||||
msRe = regexp.MustCompile(`(?i)(\d+(?:\.\d+)?)\s*ms`)
|
||||
|
||||
cpuModelZhRe = regexp.MustCompile(`(?im)^\s*CPU\s*型号\s*[::]\s*(.+?)\s*$`)
|
||||
cpuModelEnRe = regexp.MustCompile(`(?im)^\s*CPU\s*Model\s*[::]\s*(.+?)\s*$`)
|
||||
|
||||
threadScoreEnRe = regexp.MustCompile(`(?im)^\s*(\d+)\s*Thread\(s\)\s*Test\s*:\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*$`)
|
||||
threadScoreZhRe = regexp.MustCompile(`(?im)^\s*(\d+)\s*线程测试\((?:单核|多核)\)得分\s*[::]\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*$`)
|
||||
gbSingleRe = regexp.MustCompile(`(?im)^\s*Single-Core\s*Score\s*[::]\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*$`)
|
||||
gbMultiRe = regexp.MustCompile(`(?im)^\s*Multi-Core\s*Score\s*[::]\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*$`)
|
||||
|
||||
alphaNumRe = regexp.MustCompile(`[a-z0-9]+`)
|
||||
)
|
||||
|
||||
const (
|
||||
cpuStatsPrimaryURL = "https://raw.githubusercontent.com/oneclickvirt/ecs/ranks/cpu_statistics.json"
|
||||
cpuStatsFallbackURL = "https://github.com/oneclickvirt/ecs/raw/refs/heads/ranks/cpu_statistics.json"
|
||||
cpuCDNProbeTestURL = "https://raw.githubusercontent.com/spiritLHLS/ecs/main/back/test"
|
||||
cpuStatsCacheTTL = 30 * time.Minute
|
||||
cpuStatsFailCacheTTL = 5 * time.Minute
|
||||
cpuStatsRequestTimout = 6 * time.Second
|
||||
)
|
||||
|
||||
var cpuStatsCDNList = []string{
|
||||
"https://cdn.spiritlhl.net/",
|
||||
"http://cdn3.spiritlhl.net/",
|
||||
"http://cdn1.spiritlhl.net/",
|
||||
"http://cdn2.spiritlhl.net/",
|
||||
}
|
||||
|
||||
type cpuStatsEntry struct {
|
||||
CPUPrefix string `json:"cpu_prefix"`
|
||||
CPUModel string `json:"cpu_model"`
|
||||
SampleCount int `json:"sample_count"`
|
||||
MaxSingle float64 `json:"max_single_score"`
|
||||
MaxMulti float64 `json:"max_multi_score"`
|
||||
AvgSingle float64 `json:"avg_single_score"`
|
||||
AvgMulti float64 `json:"avg_multi_score"`
|
||||
Rank int `json:"rank"`
|
||||
TypicalCores int `json:"typical_cores"`
|
||||
TypicalThread int `json:"typical_threads"`
|
||||
}
|
||||
|
||||
type cpuStatsPayload struct {
|
||||
CPUStatistics []cpuStatsEntry `json:"cpu_statistics"`
|
||||
}
|
||||
|
||||
var (
|
||||
cpuStatsMu sync.Mutex
|
||||
cachedCPUStats *cpuStatsPayload
|
||||
cpuStatsExpireAt time.Time
|
||||
)
|
||||
|
||||
func parseFloatsByRegex(content string, re *regexp.Regexp) []float64 {
|
||||
matches := re.FindAllStringSubmatch(content, -1)
|
||||
vals := make([]float64, 0, len(matches))
|
||||
for _, m := range matches {
|
||||
if len(m) < 2 {
|
||||
continue
|
||||
}
|
||||
v, err := strconv.ParseFloat(m[1], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
vals = append(vals, v)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func parseFloatString(s string) (float64, bool) {
|
||||
clean := strings.ReplaceAll(strings.TrimSpace(s), ",", "")
|
||||
v, err := strconv.ParseFloat(clean, 64)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return v, true
|
||||
}
|
||||
|
||||
func extractCPUModel(output string) string {
|
||||
for _, re := range []*regexp.Regexp{cpuModelZhRe, cpuModelEnRe} {
|
||||
m := re.FindStringSubmatch(output)
|
||||
if len(m) >= 2 {
|
||||
model := strings.TrimSpace(m[1])
|
||||
if model != "" {
|
||||
return model
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractCPUScores(output string) (single float64, singleOK bool, multi float64, multiOK bool) {
|
||||
for _, re := range []*regexp.Regexp{threadScoreEnRe, threadScoreZhRe} {
|
||||
matches := re.FindAllStringSubmatch(output, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) < 3 {
|
||||
continue
|
||||
}
|
||||
threads, err := strconv.Atoi(strings.TrimSpace(m[1]))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
score, ok := parseFloatString(m[2])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if threads == 1 {
|
||||
single, singleOK = score, true
|
||||
continue
|
||||
}
|
||||
if threads > 1 && (!multiOK || score > multi) {
|
||||
multi, multiOK = score, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !singleOK {
|
||||
if m := gbSingleRe.FindStringSubmatch(output); len(m) >= 2 {
|
||||
if v, ok := parseFloatString(m[1]); ok {
|
||||
single, singleOK = v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !multiOK {
|
||||
if m := gbMultiRe.FindStringSubmatch(output); len(m) >= 2 {
|
||||
if v, ok := parseFloatString(m[1]); ok {
|
||||
multi, multiOK = v, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func normalizeCPUString(s string) string {
|
||||
s = strings.ToLower(s)
|
||||
b := strings.Builder{}
|
||||
b.Grow(len(s))
|
||||
for _, r := range s {
|
||||
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func cpuTokens(s string) []string {
|
||||
lower := strings.ToLower(s)
|
||||
raw := alphaNumRe.FindAllString(lower, -1)
|
||||
if len(raw) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := make(map[string]struct{}, len(raw))
|
||||
out := make([]string, 0, len(raw))
|
||||
for _, t := range raw {
|
||||
if len(t) < 2 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[t]; ok {
|
||||
continue
|
||||
}
|
||||
seen[t] = struct{}{}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func fuzzyScoreCPUModel(model string, entry cpuStatsEntry) float64 {
|
||||
nm := normalizeCPUString(model)
|
||||
ne := normalizeCPUString(entry.CPUModel)
|
||||
np := normalizeCPUString(entry.CPUPrefix)
|
||||
if nm == "" || (ne == "" && np == "") {
|
||||
return 0
|
||||
}
|
||||
|
||||
if nm == ne || nm == np {
|
||||
return 1
|
||||
}
|
||||
|
||||
containsScore := 0.0
|
||||
for _, candidate := range []string{ne, np} {
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(candidate, nm) || strings.Contains(nm, candidate) {
|
||||
shortLen := len(nm)
|
||||
if len(candidate) < shortLen {
|
||||
shortLen = len(candidate)
|
||||
}
|
||||
longLen := len(nm)
|
||||
if len(candidate) > longLen {
|
||||
longLen = len(candidate)
|
||||
}
|
||||
if longLen > 0 {
|
||||
ratio := float64(shortLen) / float64(longLen)
|
||||
if ratio > containsScore {
|
||||
containsScore = ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modelTokens := cpuTokens(model)
|
||||
if len(modelTokens) == 0 {
|
||||
return containsScore
|
||||
}
|
||||
|
||||
entryTokenSet := make(map[string]struct{})
|
||||
for _, t := range cpuTokens(entry.CPUModel + " " + entry.CPUPrefix) {
|
||||
entryTokenSet[t] = struct{}{}
|
||||
}
|
||||
overlap := 0
|
||||
for _, t := range modelTokens {
|
||||
if _, ok := entryTokenSet[t]; ok {
|
||||
overlap++
|
||||
}
|
||||
}
|
||||
overlapScore := float64(overlap) / float64(len(modelTokens))
|
||||
|
||||
if containsScore > overlapScore {
|
||||
return containsScore
|
||||
}
|
||||
return overlapScore
|
||||
}
|
||||
|
||||
func loadCPUStats() *cpuStatsPayload {
|
||||
cpuStatsMu.Lock()
|
||||
defer cpuStatsMu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
if now.Before(cpuStatsExpireAt) {
|
||||
return cachedCPUStats
|
||||
}
|
||||
|
||||
client := req.C()
|
||||
client.SetTimeout(cpuStatsRequestTimout)
|
||||
endpoints := []string{cpuStatsPrimaryURL, cpuStatsFallbackURL}
|
||||
|
||||
availableCDN := detectAvailableCPUCDN(client)
|
||||
for _, endpoint := range endpoints {
|
||||
urls := []string{}
|
||||
if availableCDN != "" {
|
||||
urls = append(urls, availableCDN+endpoint)
|
||||
}
|
||||
urls = append(urls, endpoint)
|
||||
|
||||
for _, u := range urls {
|
||||
payload := tryDecodeCPUStatsFromURL(client, u)
|
||||
if payload == nil {
|
||||
continue
|
||||
}
|
||||
cachedCPUStats = payload
|
||||
cpuStatsExpireAt = now.Add(cpuStatsCacheTTL)
|
||||
return cachedCPUStats
|
||||
}
|
||||
}
|
||||
|
||||
cachedCPUStats = nil
|
||||
cpuStatsExpireAt = now.Add(cpuStatsFailCacheTTL)
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectAvailableCPUCDN(client *req.Client) string {
|
||||
for _, baseURL := range cpuStatsCDNList {
|
||||
if checkCPUCDN(client, baseURL) {
|
||||
return baseURL
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkCPUCDN(client *req.Client, baseURL string) bool {
|
||||
resp, err := client.R().SetHeader("User-Agent", "goecs-summary/1.0").Get(baseURL + cpuCDNProbeTestURL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return false
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(b), "success")
|
||||
}
|
||||
|
||||
func tryDecodeCPUStatsFromURL(client *req.Client, u string) *cpuStatsPayload {
|
||||
resp, err := client.R().SetHeader("User-Agent", "goecs-summary/1.0").Get(u)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var payload cpuStatsPayload
|
||||
dec := json.NewDecoder(io.LimitReader(resp.Body, 8<<20))
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(payload.CPUStatistics) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &payload
|
||||
}
|
||||
|
||||
func matchCPUStatsEntry(model string, payload *cpuStatsPayload) *cpuStatsEntry {
|
||||
if payload == nil || model == "" || len(payload.CPUStatistics) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
trimModel := strings.TrimSpace(model)
|
||||
normModel := normalizeCPUString(trimModel)
|
||||
|
||||
for i := range payload.CPUStatistics {
|
||||
entry := &payload.CPUStatistics[i]
|
||||
if strings.EqualFold(strings.TrimSpace(entry.CPUModel), trimModel) {
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
for i := range payload.CPUStatistics {
|
||||
entry := &payload.CPUStatistics[i]
|
||||
if normModel == normalizeCPUString(entry.CPUModel) || normModel == normalizeCPUString(entry.CPUPrefix) {
|
||||
return entry
|
||||
}
|
||||
}
|
||||
|
||||
bestIdx := -1
|
||||
bestScore := 0.0
|
||||
for i := range payload.CPUStatistics {
|
||||
score := fuzzyScoreCPUModel(trimModel, payload.CPUStatistics[i])
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
bestIdx = i
|
||||
continue
|
||||
}
|
||||
if score == bestScore && bestIdx >= 0 && payload.CPUStatistics[i].SampleCount > payload.CPUStatistics[bestIdx].SampleCount {
|
||||
bestIdx = i
|
||||
}
|
||||
}
|
||||
|
||||
if bestIdx >= 0 && bestScore >= 0.45 {
|
||||
return &payload.CPUStatistics[bestIdx]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cpuTierText(score float64, lang string) string {
|
||||
if lang == "zh" {
|
||||
switch {
|
||||
case score >= 5000:
|
||||
return "按 README_NEW_USER 的 Sysbench 口径,单核 >5000 可视为高性能第一梯队。"
|
||||
case score < 500:
|
||||
return "按 README_NEW_USER 的 Sysbench 口径,单核 <500 属于偏弱性能。"
|
||||
default:
|
||||
return "按 README_NEW_USER 的 Sysbench 口径,可按每约 1000 分视作一个性能档位。"
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case score >= 5000:
|
||||
return "Per README_NEW_USER Sysbench guidance, single-core > 5000 is considered first-tier high performance."
|
||||
case score < 500:
|
||||
return "Per README_NEW_USER Sysbench guidance, single-core < 500 is considered weak performance."
|
||||
default:
|
||||
return "Per README_NEW_USER Sysbench guidance, roughly every 1000 points is about one performance tier."
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeCPUWithRanking(finalOutput, lang string) []string {
|
||||
model := extractCPUModel(finalOutput)
|
||||
single, singleOK, multi, multiOK := extractCPUScores(finalOutput)
|
||||
if !singleOK && !multiOK {
|
||||
return nil
|
||||
}
|
||||
|
||||
stats := loadCPUStats()
|
||||
entry := matchCPUStatsEntry(model, stats)
|
||||
|
||||
var score float64
|
||||
var avg float64
|
||||
var max float64
|
||||
kind := "single"
|
||||
|
||||
if singleOK && entry != nil && entry.AvgSingle > 0 && entry.MaxSingle > 0 {
|
||||
score, avg, max = single, entry.AvgSingle, entry.MaxSingle
|
||||
} else if multiOK && entry != nil && entry.AvgMulti > 0 && entry.MaxMulti > 0 {
|
||||
score, avg, max = multi, entry.AvgMulti, entry.MaxMulti
|
||||
kind = "multi"
|
||||
} else if singleOK {
|
||||
score = single
|
||||
} else {
|
||||
score = multi
|
||||
kind = "multi"
|
||||
}
|
||||
|
||||
lines := make([]string, 0, 4)
|
||||
if lang == "zh" {
|
||||
if kind == "single" {
|
||||
lines = append(lines, fmt.Sprintf("CPU: 检测到单核得分 %.2f。", score))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("CPU: 检测到多核得分 %.2f。", score))
|
||||
}
|
||||
} else {
|
||||
if kind == "single" {
|
||||
lines = append(lines, fmt.Sprintf("CPU: detected single-core score %.2f.", score))
|
||||
} else {
|
||||
lines = append(lines, fmt.Sprintf("CPU: detected multi-core score %.2f.", score))
|
||||
}
|
||||
}
|
||||
|
||||
if kind == "single" {
|
||||
lines = append(lines, cpuTierText(score, lang))
|
||||
}
|
||||
|
||||
if entry == nil || avg <= 0 || max <= 0 {
|
||||
if lang == "zh" {
|
||||
if model != "" {
|
||||
lines = append(lines, fmt.Sprintf("CPU 对标: 未在在线榜单中稳定匹配到型号 \"%s\",已仅给出本机分数解读。", model))
|
||||
} else {
|
||||
lines = append(lines, "CPU 对标: 未提取到 CPU 型号,已仅给出本机分数解读。")
|
||||
}
|
||||
} else {
|
||||
if model != "" {
|
||||
lines = append(lines, fmt.Sprintf("CPU ranking: no reliable online match found for model \"%s\"; local score interpretation only.", model))
|
||||
} else {
|
||||
lines = append(lines, "CPU ranking: CPU model not found in output; local score interpretation only.")
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
reachAvg := score >= avg
|
||||
gapToMax := max - score
|
||||
fullBlood := false
|
||||
if max > 0 {
|
||||
ratioDiff := (score - max) / max
|
||||
if ratioDiff < 0 {
|
||||
ratioDiff = -ratioDiff
|
||||
}
|
||||
fullBlood = ratioDiff <= 0.05
|
||||
}
|
||||
pctOfAvg := score / avg * 100
|
||||
pctOfMax := score / max * 100
|
||||
|
||||
if lang == "zh" {
|
||||
lines = append(lines,
|
||||
fmt.Sprintf("CPU 对标: 匹配 \"%s\"(样本 %d,排名 #%d)。", entry.CPUModel, entry.SampleCount, entry.Rank),
|
||||
fmt.Sprintf("平均分达标: %s(本机 %.2f,均值 %.2f,达成率 %.2f%%)。", map[bool]string{true: "是", false: "否"}[reachAvg], score, avg, pctOfAvg),
|
||||
fmt.Sprintf("满血对比: 满血分 %.2f,本机为 %.2f%%,差值 %.2f。", max, pctOfMax, gapToMax),
|
||||
fmt.Sprintf("满血判定(±5%%波动): %s。", map[bool]string{true: "是", false: "否"}[fullBlood]),
|
||||
)
|
||||
} else {
|
||||
lines = append(lines,
|
||||
fmt.Sprintf("CPU ranking: matched \"%s\" (samples %d, rank #%d).", entry.CPUModel, entry.SampleCount, entry.Rank),
|
||||
fmt.Sprintf("Average-level check: %s (local %.2f vs avg %.2f, %.2f%% of avg).", map[bool]string{true: "pass", false: "below avg"}[reachAvg], score, avg, pctOfAvg),
|
||||
fmt.Sprintf("Full-blood comparison: max %.2f, local is %.2f%% of max, gap %.2f.", max, pctOfMax, gapToMax),
|
||||
fmt.Sprintf("Full-blood status (within ±5%%): %s.", map[bool]string{true: "yes", false: "no"}[fullBlood]),
|
||||
)
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func summarizeBandwidth(vals []float64, lang string) string {
|
||||
if len(vals) == 0 {
|
||||
if lang == "zh" {
|
||||
return "测速: 未检测到有效 Mbps 数据。"
|
||||
}
|
||||
return "Speed: no valid Mbps values found."
|
||||
}
|
||||
sort.Float64s(vals)
|
||||
maxV := vals[len(vals)-1]
|
||||
if lang == "zh" {
|
||||
switch {
|
||||
case maxV >= 2000:
|
||||
return fmt.Sprintf("测速: 峰值约 %.2f Mbps,属于高带宽网络。", maxV)
|
||||
case maxV >= 800:
|
||||
return fmt.Sprintf("测速: 峰值约 %.2f Mbps,带宽表现较好。", maxV)
|
||||
case maxV >= 200:
|
||||
return fmt.Sprintf("测速: 峰值约 %.2f Mbps,带宽中等可用。", maxV)
|
||||
default:
|
||||
return fmt.Sprintf("测速: 峰值约 %.2f Mbps,带宽偏低,建议关注线路与机型。", maxV)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case maxV >= 2000:
|
||||
return fmt.Sprintf("Speed: peak around %.2f Mbps, high-bandwidth profile.", maxV)
|
||||
case maxV >= 800:
|
||||
return fmt.Sprintf("Speed: peak around %.2f Mbps, strong bandwidth performance.", maxV)
|
||||
case maxV >= 200:
|
||||
return fmt.Sprintf("Speed: peak around %.2f Mbps, moderate and usable bandwidth.", maxV)
|
||||
default:
|
||||
return fmt.Sprintf("Speed: peak around %.2f Mbps, relatively limited bandwidth.", maxV)
|
||||
}
|
||||
}
|
||||
|
||||
func summarizeLatency(vals []float64, lang string) string {
|
||||
if len(vals) == 0 {
|
||||
if lang == "zh" {
|
||||
return "延迟: 未检测到有效 ms 数据。"
|
||||
}
|
||||
return "Latency: no valid ms values found."
|
||||
}
|
||||
sort.Float64s(vals)
|
||||
minV := vals[0]
|
||||
if lang == "zh" {
|
||||
switch {
|
||||
case minV <= 15:
|
||||
return fmt.Sprintf("延迟: 最优约 %.2f ms,实时交互体验优秀。", minV)
|
||||
case minV <= 45:
|
||||
return fmt.Sprintf("延迟: 最优约 %.2f ms,整体交互体验良好。", minV)
|
||||
case minV <= 90:
|
||||
return fmt.Sprintf("延迟: 最优约 %.2f ms,可用但有一定时延。", minV)
|
||||
default:
|
||||
return fmt.Sprintf("延迟: 最优约 %.2f ms,时延偏高,建议优化线路。", minV)
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case minV <= 15:
|
||||
return fmt.Sprintf("Latency: best around %.2f ms, excellent for interactive workloads.", minV)
|
||||
case minV <= 45:
|
||||
return fmt.Sprintf("Latency: best around %.2f ms, generally responsive.", minV)
|
||||
case minV <= 90:
|
||||
return fmt.Sprintf("Latency: best around %.2f ms, usable with moderate delay.", minV)
|
||||
default:
|
||||
return fmt.Sprintf("Latency: best around %.2f ms, relatively high and may impact responsiveness.", minV)
|
||||
}
|
||||
}
|
||||
|
||||
func testedScopes(config *params.Config) []string {
|
||||
scopes := make([]string, 0, 8)
|
||||
if config.BasicStatus {
|
||||
scopes = append(scopes, "basic")
|
||||
}
|
||||
if config.CpuTestStatus {
|
||||
scopes = append(scopes, "cpu")
|
||||
}
|
||||
if config.MemoryTestStatus {
|
||||
scopes = append(scopes, "memory")
|
||||
}
|
||||
if config.DiskTestStatus {
|
||||
scopes = append(scopes, "disk")
|
||||
}
|
||||
if config.UtTestStatus {
|
||||
scopes = append(scopes, "unlock")
|
||||
}
|
||||
if config.SecurityTestStatus {
|
||||
scopes = append(scopes, "security")
|
||||
}
|
||||
if config.Nt3Status || config.BacktraceStatus || config.PingTestStatus || config.TgdcTestStatus || config.WebTestStatus {
|
||||
scopes = append(scopes, "network")
|
||||
}
|
||||
if config.SpeedTestStatus {
|
||||
scopes = append(scopes, "speed")
|
||||
}
|
||||
return scopes
|
||||
}
|
||||
|
||||
func scopesText(scopes []string, lang string) string {
|
||||
if len(scopes) == 0 {
|
||||
if lang == "zh" {
|
||||
return "无"
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
labelsZh := map[string]string{
|
||||
"basic": "系统基础", "cpu": "CPU", "memory": "内存", "disk": "磁盘", "unlock": "解锁", "security": "IP质量", "network": "网络路由", "speed": "带宽测速",
|
||||
}
|
||||
labelsEn := map[string]string{
|
||||
"basic": "system basics", "cpu": "CPU", "memory": "memory", "disk": "disk", "unlock": "unlock", "security": "IP quality", "network": "network route", "speed": "bandwidth",
|
||||
}
|
||||
out := make([]string, 0, len(scopes))
|
||||
for _, s := range scopes {
|
||||
if lang == "zh" {
|
||||
out = append(out, labelsZh[s])
|
||||
} else {
|
||||
out = append(out, labelsEn[s])
|
||||
}
|
||||
}
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
|
||||
// GenerateSummary creates a concise post-test summary from final output.
|
||||
func GenerateSummary(config *params.Config, finalOutput string) string {
|
||||
lang := config.Language
|
||||
scopes := testedScopes(config)
|
||||
bandwidthVals := parseFloatsByRegex(finalOutput, mbpsRe)
|
||||
latencyVals := parseFloatsByRegex(finalOutput, msRe)
|
||||
cpuLines := summarizeCPUWithRanking(finalOutput, lang)
|
||||
|
||||
if lang == "zh" {
|
||||
lines := []string{
|
||||
"测试结果总结:",
|
||||
fmt.Sprintf("- 本次覆盖: %s", scopesText(scopes, lang)),
|
||||
}
|
||||
for _, line := range cpuLines {
|
||||
lines = append(lines, "- "+line)
|
||||
}
|
||||
if config.SpeedTestStatus {
|
||||
lines = append(lines, "- "+summarizeBandwidth(bandwidthVals, lang))
|
||||
lines = append(lines, "- 参考 README_NEW_USER: 一般境外机器带宽 100Mbps 起步,是否够用应以业务下载/传输需求为准。")
|
||||
}
|
||||
if config.PingTestStatus || config.TgdcTestStatus || config.WebTestStatus || config.BacktraceStatus || config.Nt3Status {
|
||||
lines = append(lines, "- "+summarizeLatency(latencyVals, lang))
|
||||
lines = append(lines, "- 参考 README_NEW_USER: 延迟 >= 9999ms 可视为目标不可用。")
|
||||
}
|
||||
lines = append(lines, "- 建议: 结合业务场景(高并发计算/存储/跨境网络)重点参考对应分项。")
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
lines := []string{
|
||||
"Test Summary:",
|
||||
fmt.Sprintf("- Scope covered: %s", scopesText(scopes, lang)),
|
||||
}
|
||||
for _, line := range cpuLines {
|
||||
lines = append(lines, "- "+line)
|
||||
}
|
||||
if config.SpeedTestStatus {
|
||||
lines = append(lines, "- "+summarizeBandwidth(bandwidthVals, lang))
|
||||
lines = append(lines, "- README_NEW_USER note: offshore servers commonly start around 100Mbps; evaluate against your actual workload needs.")
|
||||
}
|
||||
if config.PingTestStatus || config.TgdcTestStatus || config.WebTestStatus || config.BacktraceStatus || config.Nt3Status {
|
||||
lines = append(lines, "- "+summarizeLatency(latencyVals, lang))
|
||||
lines = append(lines, "- README_NEW_USER note: latency >= 9999ms should be treated as unavailable target.")
|
||||
}
|
||||
lines = append(lines, "- Suggestion: prioritize the metrics that match your workload (compute, storage, or cross-region networking).")
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
344
internal/menu/menu.go
Normal file
344
internal/menu/menu.go
Normal file
@@ -0,0 +1,344 @@
|
||||
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 {
|
||||
config.Choice = "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")
|
||||
}
|
||||
}
|
||||
999
internal/menu/tui.go
Normal file
999
internal/menu/tui.go
Normal file
@@ -0,0 +1,999 @@
|
||||
package menu
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
textinput "github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/oneclickvirt/ecs/internal/params"
|
||||
"github.com/oneclickvirt/ecs/utils"
|
||||
)
|
||||
|
||||
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)
|
||||
tPanelStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("238")).Padding(0, 1)
|
||||
)
|
||||
|
||||
type menuPhase int
|
||||
|
||||
const (
|
||||
phaseLang menuPhase = iota
|
||||
phaseMain
|
||||
phaseCustom
|
||||
)
|
||||
|
||||
type mainMenuItem struct {
|
||||
id string
|
||||
zh string
|
||||
en string
|
||||
descZh string
|
||||
descEn string
|
||||
needNet bool
|
||||
}
|
||||
|
||||
type testToggle struct {
|
||||
key string
|
||||
nameZh string
|
||||
nameEn string
|
||||
descZh string
|
||||
descEn string
|
||||
enabled bool
|
||||
needNet bool
|
||||
}
|
||||
|
||||
type advOption struct {
|
||||
value string
|
||||
labelZh string
|
||||
labelEn string
|
||||
descZh string
|
||||
descEn string
|
||||
}
|
||||
|
||||
type advSetting struct {
|
||||
key string
|
||||
nameZh string
|
||||
nameEn string
|
||||
descZh string
|
||||
descEn string
|
||||
kind string // option | bool | text
|
||||
options []advOption
|
||||
current int
|
||||
boolVal bool
|
||||
textVal string
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
langCursor int
|
||||
mainCursor int
|
||||
mainItems []mainMenuItem
|
||||
|
||||
customCursor int
|
||||
toggles []testToggle
|
||||
advanced []advSetting
|
||||
customTotal int
|
||||
|
||||
editingText bool
|
||||
editingIdx int
|
||||
textInput textinput.Model
|
||||
|
||||
statsTotal int
|
||||
statsDaily int
|
||||
hasStats bool
|
||||
cmpVersion int
|
||||
newVersion string
|
||||
|
||||
result tuiResult
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
func defaultMainItems() []mainMenuItem {
|
||||
return []mainMenuItem{
|
||||
{id: "1", zh: "融合怪完全体(能测全测)", en: "Full Test (All Available Tests)", descZh: "系统信息、CPU、内存、磁盘、解锁、IP质量、邮件端口、回程、NT3、测速、TGDC、网站延迟。", descEn: "Runs all available modules: system, compute, memory, disk, unlock, security, routing and speed.", needNet: false},
|
||||
{id: "2", zh: "极简版", en: "Minimal Suite", descZh: "基础系统+CPU+内存+磁盘+5个测速节点,适合快速健康检查。", descEn: "Basic system + CPU + memory + disk + 5 speed nodes for quick health checks.", needNet: false},
|
||||
{id: "3", zh: "精简版", en: "Standard Suite", descZh: "在极简版基础上增加平台解锁与路由能力评估。", descEn: "Minimal suite plus unlock and routing capability checks.", needNet: false},
|
||||
{id: "4", zh: "精简网络版", en: "Network Suite", descZh: "强调网络回程与路由质量,辅以基础硬件测试。", descEn: "Network-focused profile with backtrace/routing plus basic hardware checks.", needNet: false},
|
||||
{id: "5", zh: "精简解锁版", en: "Unlock Suite", descZh: "以流媒体和平台解锁能力为主,附基础性能测试。", descEn: "Unlock-focused profile with essential compute/storage checks.", needNet: false},
|
||||
{id: "6", zh: "网络单项", en: "Network Only", descZh: "仅网络维度:IP质量、回程、NT3、延迟、TGDC、网站和测速。", descEn: "Network-only profile: IP quality, route, latency, TGDC, websites, speed.", needNet: true},
|
||||
{id: "7", zh: "解锁单项", en: "Unlock Only", descZh: "仅进行跨国平台解锁与流媒体可用性检测。", descEn: "Unlock-only profile for cross-border media/service availability.", needNet: true},
|
||||
{id: "8", zh: "硬件单项", en: "Hardware Only", descZh: "系统信息、CPU、内存、dd/fio 磁盘测试。", descEn: "Hardware-only profile with system, CPU, memory and disk tests.", needNet: false},
|
||||
{id: "9", zh: "IP质量检测", en: "IP Quality", descZh: "15库 IP质量 + 邮件端口,适合网络身份风险评估。", descEn: "IP quality across multiple datasets plus email port checks.", needNet: true},
|
||||
{id: "10", zh: "三网回程线路", en: "3-Network Route", descZh: "三网回程、NT3路由、延迟、TGDC、网站延迟专项。", descEn: "3-network backtrace + NT3 route + latency/TGDC/website checks.", needNet: true},
|
||||
{id: "custom", zh: ">>> 高级自定义(全参数模式)", en: ">>> Advanced Custom (Full Parameters)", descZh: "按参数逐项配置,支持测试项、方法、路径、上传和结果分析。", descEn: "Configure per-parameter with test toggles, methods, paths, upload and analysis.", needNet: false},
|
||||
{id: "0", zh: "退出程序", en: "Exit Program", descZh: "退出当前程序。", descEn: "Exit program.", needNet: false},
|
||||
}
|
||||
}
|
||||
|
||||
func defaultTestToggles() []testToggle {
|
||||
return []testToggle{
|
||||
{key: "basic", nameZh: "基础系统信息", nameEn: "Basic System Info", descZh: "操作系统、CPU型号、内核、虚拟化等基础信息。", descEn: "OS, CPU model, kernel, virtualization and base environment info.", enabled: true, needNet: false},
|
||||
{key: "cpu", nameZh: "CPU测试", nameEn: "CPU Test", descZh: "按所选方法执行 CPU 计算性能测试。", descEn: "Run CPU compute benchmarks using selected method.", enabled: true, needNet: false},
|
||||
{key: "memory", nameZh: "内存测试", nameEn: "Memory Test", descZh: "按所选方法测试内存吞吐和访问性能。", descEn: "Run memory throughput and access benchmarks by selected method.", enabled: true, needNet: false},
|
||||
{key: "disk", nameZh: "磁盘测试", nameEn: "Disk Test", descZh: "按所选方法执行磁盘读写性能测试。", descEn: "Run disk read/write benchmark using selected method/path.", enabled: true, needNet: false},
|
||||
{key: "ut", nameZh: "跨国平台解锁", nameEn: "Streaming Unlock", descZh: "检测多类海外流媒体与服务可用性。", descEn: "Check availability of cross-border streaming/services.", enabled: false, needNet: true},
|
||||
{key: "security", nameZh: "IP质量检测", nameEn: "IP Quality Check", descZh: "多库 IP 信誉、风险和质量信息检测。", descEn: "IP reputation/risk/quality checks across multiple datasets.", enabled: false, needNet: true},
|
||||
{key: "email", nameZh: "邮件端口检测", nameEn: "Email Port Check", descZh: "检查常见邮件相关端口连通能力。", descEn: "Check common mail-related port connectivity.", enabled: false, needNet: true},
|
||||
{key: "backtrace", nameZh: "回程路由", nameEn: "Backtrace Route", descZh: "检测上游及三网回程路径。", descEn: "Inspect upstream and 3-network return routes.", enabled: false, needNet: true},
|
||||
{key: "nt3", nameZh: "NT3路由", nameEn: "NT3 Route", descZh: "按指定地区与协议执行详细路由追踪。", descEn: "Run detailed route trace by selected location/protocol.", enabled: false, needNet: true},
|
||||
{key: "speed", nameZh: "测速", nameEn: "Speed Test", descZh: "测试下载/上传带宽与延迟。", descEn: "Measure download/upload bandwidth and latency.", enabled: false, needNet: true},
|
||||
{key: "ping", nameZh: "Ping测试", nameEn: "Ping Test", descZh: "全国/多地区延迟质量测试。", descEn: "Latency quality checks across multiple regions.", enabled: false, needNet: true},
|
||||
{key: "tgdc", nameZh: "Telegram DC测试", nameEn: "Telegram DC Test", descZh: "检测 Telegram 数据中心延迟表现。", descEn: "Measure latency to Telegram data centers.", enabled: false, needNet: true},
|
||||
{key: "web", nameZh: "网站延迟", nameEn: "Website Latency", descZh: "检测常见网站访问延迟。", descEn: "Check latency to commonly used websites.", enabled: false, needNet: true},
|
||||
}
|
||||
}
|
||||
|
||||
func option(value, zh, en, descZh, descEn string) advOption {
|
||||
return advOption{value: value, labelZh: zh, labelEn: en, descZh: descZh, descEn: descEn}
|
||||
}
|
||||
|
||||
func defaultAdvSettings(config *params.Config) []advSetting {
|
||||
adv := []advSetting{
|
||||
{
|
||||
key: "cpum", nameZh: "CPU测试方法", nameEn: "CPU Method", kind: "option",
|
||||
descZh: "选择 CPU 压测方法,不同方法偏向不同负载模型。",
|
||||
descEn: "Choose CPU benchmark method. Different methods model different workloads.",
|
||||
options: []advOption{
|
||||
option("sysbench", "Sysbench", "Sysbench", "通用基准,稳定易比较。", "General-purpose benchmark with stable comparability."),
|
||||
option("geekbench", "Geekbench", "Geekbench", "偏综合应用模型,便于横向对比。", "Application-like synthetic benchmark for broad comparison."),
|
||||
option("winsat", "WinSAT", "WinSAT", "Windows 场景下常用基准。", "Common benchmark in Windows environments."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "cput", nameZh: "CPU线程模式", nameEn: "CPU Thread Mode", kind: "option",
|
||||
descZh: "单线程看核心峰值,多线程看整机并发能力。",
|
||||
descEn: "Single-core shows peak core power; multi-core shows parallel throughput.",
|
||||
options: []advOption{
|
||||
option("multi", "多线程", "Multi-thread", "评估整机并发算力。", "Evaluate full-machine parallel compute capability."),
|
||||
option("single", "单线程", "Single-thread", "评估单核心峰值性能。", "Evaluate peak single-core performance."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "memorym", nameZh: "内存测试方法", nameEn: "Memory Method", kind: "option",
|
||||
descZh: "选择内存测试方法,结果关注带宽与访问效率。",
|
||||
descEn: "Choose memory benchmark method to evaluate bandwidth/access efficiency.",
|
||||
options: []advOption{
|
||||
option("stream", "STREAM", "STREAM", "侧重带宽测试。", "Focused on memory bandwidth."),
|
||||
option("sysbench", "Sysbench", "Sysbench", "通用内存压测。", "General-purpose memory stress benchmark."),
|
||||
option("dd", "dd", "dd", "基于系统工具的简化测试。", "Simple system-tool-based measurement."),
|
||||
option("winsat", "WinSAT", "WinSAT", "Windows 环境内存基准。", "Windows-oriented memory benchmark."),
|
||||
option("auto", "自动", "Auto", "自动选择可用且优先方法。", "Automatically select the preferred available method."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "diskm", nameZh: "磁盘测试方法", nameEn: "Disk Method", kind: "option",
|
||||
descZh: "选择磁盘测试方法,评估顺序/随机读写能力。",
|
||||
descEn: "Choose disk method to evaluate sequential/random I/O performance.",
|
||||
options: []advOption{
|
||||
option("fio", "FIO", "FIO", "更全面的磁盘 I/O 基准。", "Comprehensive disk I/O benchmark."),
|
||||
option("dd", "dd", "dd", "快速顺序写读基准。", "Quick sequential write/read benchmark."),
|
||||
option("winsat", "WinSAT", "WinSAT", "Windows 磁盘基准。", "Disk benchmark for Windows environments."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "diskp", nameZh: "磁盘测试路径", nameEn: "Disk Test Path", kind: "text",
|
||||
descZh: "自定义磁盘测试目录。留空表示默认路径。",
|
||||
descEn: "Custom disk test directory. Empty means default path.",
|
||||
textVal: config.DiskTestPath,
|
||||
},
|
||||
{
|
||||
key: "diskmc", nameZh: "多磁盘检测", nameEn: "Multi-Disk Check", kind: "bool",
|
||||
descZh: "启用后尝试检测并测试多磁盘路径。",
|
||||
descEn: "When enabled, detect and benchmark multiple disk paths.",
|
||||
boolVal: config.DiskMultiCheck,
|
||||
},
|
||||
{
|
||||
key: "nt3loc", nameZh: "NT3测试地区", nameEn: "NT3 Location", kind: "option",
|
||||
descZh: "选择路由追踪地区。显示中文全称,内部仍使用标准参数值。",
|
||||
descEn: "Choose route trace region. Full names are shown while preserving standard values.",
|
||||
options: []advOption{
|
||||
option("GZ", "广州", "Guangzhou", "从广州节点进行追踪。", "Trace from Guangzhou node."),
|
||||
option("SH", "上海", "Shanghai", "从上海节点进行追踪。", "Trace from Shanghai node."),
|
||||
option("BJ", "北京", "Beijing", "从北京节点进行追踪。", "Trace from Beijing node."),
|
||||
option("CD", "成都", "Chengdu", "从成都节点进行追踪。", "Trace from Chengdu node."),
|
||||
option("ALL", "全部地区", "All Regions", "依次测试全部地区节点。", "Run route traces from all supported regions."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "nt3t", nameZh: "NT3协议类型", nameEn: "NT3 Protocol", kind: "option",
|
||||
descZh: "指定 NT3 路由检测协议栈。",
|
||||
descEn: "Select protocol stack used by NT3 route checks.",
|
||||
options: []advOption{
|
||||
option("ipv4", "仅 IPv4", "IPv4 Only", "仅测试 IPv4 路由路径。", "Test IPv4 routing only."),
|
||||
option("ipv6", "仅 IPv6", "IPv6 Only", "仅测试 IPv6 路由路径。", "Test IPv6 routing only."),
|
||||
option("both", "IPv4 + IPv6", "IPv4 + IPv6", "同时测试 IPv4 与 IPv6。", "Test both IPv4 and IPv6."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "spnum", nameZh: "测速节点数/运营商", nameEn: "Speed Nodes per ISP", kind: "option",
|
||||
descZh: "每个运营商选择的测速节点数量。",
|
||||
descEn: "Number of speed test nodes selected per ISP.",
|
||||
options: []advOption{
|
||||
option("1", "1 个", "1 node", "最快速,覆盖最少。", "Fastest run with least coverage."),
|
||||
option("2", "2 个", "2 nodes", "默认平衡。", "Default balanced option."),
|
||||
option("3", "3 个", "3 nodes", "覆盖更广,耗时增加。", "Broader coverage with more runtime."),
|
||||
option("4", "4 个", "4 nodes", "更完整网络采样。", "More complete network sampling."),
|
||||
option("5", "5 个", "5 nodes", "高覆盖,耗时较高。", "High coverage with longer runtime."),
|
||||
option("6", "6 个", "6 nodes", "深度采样。", "Deep sampling."),
|
||||
option("7", "7 个", "7 nodes", "深度采样。", "Deep sampling."),
|
||||
option("8", "8 个", "8 nodes", "深度采样。", "Deep sampling."),
|
||||
option("9", "9 个", "9 nodes", "深度采样。", "Deep sampling."),
|
||||
option("10", "10 个", "10 nodes", "最全面,耗时最高。", "Most comprehensive, longest runtime."),
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "log", nameZh: "调试日志", nameEn: "Debug Logger", kind: "bool",
|
||||
descZh: "启用后输出更多调试日志,便于排障。",
|
||||
descEn: "Enable verbose logs for troubleshooting.",
|
||||
boolVal: config.EnableLogger,
|
||||
},
|
||||
{
|
||||
key: "upload", nameZh: "上传并生成分享链接", nameEn: "Upload & Share Link", kind: "bool",
|
||||
descZh: "启用后上传测试结果并生成可分享链接。",
|
||||
descEn: "Upload final result and generate a shareable link.",
|
||||
boolVal: config.EnableUpload,
|
||||
},
|
||||
{
|
||||
key: "analysis", nameZh: "测试后结果总结分析", nameEn: "Post-Test Summary Analysis", kind: "bool",
|
||||
descZh: "测试结束后生成简明总结,提炼优势、短板和用途建议。",
|
||||
descEn: "Generate concise final summary with strengths, limits and usage hints.",
|
||||
boolVal: config.AnalyzeResult,
|
||||
},
|
||||
{
|
||||
key: "filepath", nameZh: "结果文件名", nameEn: "Result File Name", kind: "text",
|
||||
descZh: "上传前本地结果文件名。",
|
||||
descEn: "Local result filename used before upload.",
|
||||
textVal: config.FilePath,
|
||||
},
|
||||
{
|
||||
key: "width", nameZh: "输出宽度", nameEn: "Output Width", kind: "option",
|
||||
descZh: "控制终端输出排版宽度。",
|
||||
descEn: "Controls console output formatting width.",
|
||||
options: []advOption{
|
||||
option("72", "72 列", "72 cols", "紧凑显示。", "Compact layout."),
|
||||
option("82", "82 列", "82 cols", "默认宽度。", "Default width."),
|
||||
option("100", "100 列", "100 cols", "更宽显示。", "Wider layout."),
|
||||
option("120", "120 列", "120 cols", "宽屏显示。", "Wide-screen layout."),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range adv {
|
||||
switch adv[i].key {
|
||||
case "cpum":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.CpuTestMethod)
|
||||
case "cput":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.CpuTestThreadMode)
|
||||
case "memorym":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.MemoryTestMethod)
|
||||
case "diskm":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.DiskTestMethod)
|
||||
case "nt3loc":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.Nt3Location)
|
||||
case "nt3t":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, config.Nt3CheckType)
|
||||
case "spnum":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, strconv.Itoa(config.SpNum))
|
||||
case "width":
|
||||
adv[i].current = optionIndexByValue(adv[i].options, strconv.Itoa(config.Width))
|
||||
}
|
||||
}
|
||||
|
||||
return adv
|
||||
}
|
||||
|
||||
func optionIndexByValue(options []advOption, value string) int {
|
||||
for i, opt := range options {
|
||||
if opt.value == value {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
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)
|
||||
ti := textinput.New()
|
||||
ti.Prompt = "> "
|
||||
ti.Placeholder = ""
|
||||
ti.CharLimit = 255
|
||||
ti.Width = 45
|
||||
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: config.Width,
|
||||
height: 24,
|
||||
textInput: ti,
|
||||
}
|
||||
if langPreset {
|
||||
m.phase = phaseMain
|
||||
m.result.language = config.Language
|
||||
} else {
|
||||
m.phase = phaseLang
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
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 ""
|
||||
}
|
||||
|
||||
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 := []string{"1. 中文", "2. English"}
|
||||
for i, l := range langs {
|
||||
cursor := " "
|
||||
style := tNormStyle
|
||||
if m.langCursor == i {
|
||||
cursor = " > "
|
||||
style = tSelStyle
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%s%s\n", cursor, style.Render(l)))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
s.WriteString(tHelpStyle.Render(" ↑/↓ Navigate Enter Confirm 1/2 Quick-Select q Quit"))
|
||||
s.WriteString("\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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:
|
||||
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) selectedMainDesc(lang string) string {
|
||||
item := m.mainItems[m.mainCursor]
|
||||
if lang == "zh" {
|
||||
return item.descZh
|
||||
}
|
||||
return item.descEn
|
||||
}
|
||||
|
||||
func (m tuiModel) viewMain() string {
|
||||
lang := m.result.language
|
||||
var s strings.Builder
|
||||
s.WriteString("\n")
|
||||
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")
|
||||
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", m.newVersion)))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
}
|
||||
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")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 请选择测试方案:"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Select Test Suite:"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
for i, item := range m.mainItems {
|
||||
cursor := " "
|
||||
style := tNormStyle
|
||||
if m.mainCursor == i {
|
||||
cursor = " > "
|
||||
style = tSelStyle
|
||||
}
|
||||
label := item.en
|
||||
if lang == "zh" {
|
||||
label = item.zh
|
||||
}
|
||||
prefix := ""
|
||||
switch {
|
||||
case item.id == "custom":
|
||||
prefix = ""
|
||||
case item.id == "0":
|
||||
prefix = " 0. "
|
||||
default:
|
||||
prefix = fmt.Sprintf("%2s. ", item.id)
|
||||
}
|
||||
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")
|
||||
panelTitle := " 当前选项说明"
|
||||
panelBody := m.selectedMainDesc(lang)
|
||||
if lang == "en" {
|
||||
panelTitle = " Selected Option Description"
|
||||
}
|
||||
s.WriteString(tSectStyle.Render(panelTitle) + "\n")
|
||||
s.WriteString(tPanelStyle.Width(maxInt(60, m.width-6)).Render(panelBody) + "\n")
|
||||
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()
|
||||
}
|
||||
|
||||
func (m *tuiModel) startEditText(settingIdx int) {
|
||||
m.editingText = true
|
||||
m.editingIdx = settingIdx
|
||||
m.textInput.SetValue(m.advanced[settingIdx].textVal)
|
||||
m.textInput.Focus()
|
||||
}
|
||||
|
||||
func (m *tuiModel) stopEditText(save bool) {
|
||||
if save {
|
||||
m.advanced[m.editingIdx].textVal = strings.TrimSpace(m.textInput.Value())
|
||||
}
|
||||
m.textInput.Blur()
|
||||
m.editingText = false
|
||||
}
|
||||
|
||||
func (m tuiModel) updateCustom(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
if m.editingText {
|
||||
switch msg.String() {
|
||||
case "enter":
|
||||
m.stopEditText(true)
|
||||
return m, nil
|
||||
case "esc":
|
||||
m.stopEditText(false)
|
||||
return m, nil
|
||||
case "ctrl+c":
|
||||
m.result.quit = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
return m, 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 " ", "enter", "right", "l", "left", "h":
|
||||
if m.customCursor < len(m.toggles) {
|
||||
t := &m.toggles[m.customCursor]
|
||||
if t.needNet && !m.preCheck.Connected {
|
||||
break
|
||||
}
|
||||
t.enabled = !t.enabled
|
||||
break
|
||||
}
|
||||
if m.customCursor == m.customTotal-1 {
|
||||
m.result.custom = true
|
||||
m.result.choice = "custom"
|
||||
m.result.toggles = m.toggles
|
||||
m.result.advanced = m.advanced
|
||||
return m, tea.Quit
|
||||
}
|
||||
advIdx := m.customCursor - len(m.toggles)
|
||||
if advIdx >= 0 && advIdx < len(m.advanced) {
|
||||
a := &m.advanced[advIdx]
|
||||
switch a.kind {
|
||||
case "bool":
|
||||
a.boolVal = !a.boolVal
|
||||
case "option":
|
||||
if key == "left" || key == "h" {
|
||||
a.current = (a.current - 1 + len(a.options)) % len(a.options)
|
||||
} else {
|
||||
a.current = (a.current + 1) % len(a.options)
|
||||
}
|
||||
case "text":
|
||||
if key == "enter" || key == " " {
|
||||
m.startEditText(advIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "a":
|
||||
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 "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) currentCustomDescription(lang string) string {
|
||||
if m.customCursor < len(m.toggles) {
|
||||
t := m.toggles[m.customCursor]
|
||||
if lang == "zh" {
|
||||
return t.descZh
|
||||
}
|
||||
return t.descEn
|
||||
}
|
||||
if m.customCursor == m.customTotal-1 {
|
||||
if lang == "zh" {
|
||||
return "确认当前高级自定义配置并开始测试。"
|
||||
}
|
||||
return "Confirm current advanced custom configuration and start tests."
|
||||
}
|
||||
idx := m.customCursor - len(m.toggles)
|
||||
a := m.advanced[idx]
|
||||
if a.kind == "option" {
|
||||
op := a.options[a.current]
|
||||
if lang == "zh" {
|
||||
return a.descZh + " 当前选项: " + op.labelZh + "。" + op.descZh
|
||||
}
|
||||
return a.descEn + " Current option: " + op.labelEn + ". " + op.descEn
|
||||
}
|
||||
if a.kind == "bool" {
|
||||
if lang == "zh" {
|
||||
state := "关闭"
|
||||
if a.boolVal {
|
||||
state = "开启"
|
||||
}
|
||||
return a.descZh + " 当前状态: " + state + "。"
|
||||
}
|
||||
state := "OFF"
|
||||
if a.boolVal {
|
||||
state = "ON"
|
||||
}
|
||||
return a.descEn + " Current state: " + state + "."
|
||||
}
|
||||
if lang == "zh" {
|
||||
return a.descZh + " 当前值: " + a.textVal
|
||||
}
|
||||
return a.descEn + " Current value: " + a.textVal
|
||||
}
|
||||
|
||||
func (m tuiModel) advDisplayValue(a advSetting, lang string) string {
|
||||
switch a.kind {
|
||||
case "option":
|
||||
op := a.options[a.current]
|
||||
if lang == "zh" {
|
||||
return op.labelZh
|
||||
}
|
||||
return op.labelEn
|
||||
case "bool":
|
||||
if a.boolVal {
|
||||
if lang == "zh" {
|
||||
return "开启"
|
||||
}
|
||||
return "ON"
|
||||
}
|
||||
if lang == "zh" {
|
||||
return "关闭"
|
||||
}
|
||||
return "OFF"
|
||||
case "text":
|
||||
if strings.TrimSpace(a.textVal) == "" {
|
||||
if lang == "zh" {
|
||||
return "(默认)"
|
||||
}
|
||||
return "(default)"
|
||||
}
|
||||
return a.textVal
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
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(" Advanced Custom Parameter Mode"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 测试开关 (空格切换, a 全选/全不选):"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Test Toggles (Space to toggle, a all/none):"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
for i, t := range m.toggles {
|
||||
cursor := " "
|
||||
style := tNormStyle
|
||||
if m.customCursor == i {
|
||||
cursor = " > "
|
||||
style = tSelStyle
|
||||
}
|
||||
if t.needNet && !m.preCheck.Connected {
|
||||
style = tDimStyle
|
||||
}
|
||||
check := tChkOffStyle.Render("[ ]")
|
||||
if t.enabled {
|
||||
check = tChkOnStyle.Render("[x]")
|
||||
}
|
||||
name := t.nameEn
|
||||
if lang == "zh" {
|
||||
name = t.nameZh
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%s%s %s\n", cursor, check, style.Render(name)))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tSectStyle.Render(" 参数设置 (Enter/空格切换, ←/→改选项):"))
|
||||
} else {
|
||||
s.WriteString(tSectStyle.Render(" Parameter Settings (Enter/Space switch, Left/Right cycle):"))
|
||||
}
|
||||
s.WriteString("\n\n")
|
||||
for i, a := range m.advanced {
|
||||
idx := len(m.toggles) + i
|
||||
cursor := " "
|
||||
if m.customCursor == idx {
|
||||
cursor = " > "
|
||||
}
|
||||
style := tNormStyle
|
||||
if m.customCursor == idx {
|
||||
style = tSelStyle
|
||||
}
|
||||
name := a.nameEn
|
||||
if lang == "zh" {
|
||||
name = a.nameZh
|
||||
}
|
||||
value := m.advDisplayValue(a, lang)
|
||||
if a.kind == "option" {
|
||||
value = "< " + value + " >"
|
||||
}
|
||||
s.WriteString(fmt.Sprintf("%s%-26s %s\n", cursor, style.Render(name+":"), tDimStyle.Render(value)))
|
||||
}
|
||||
|
||||
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")
|
||||
panelTitle := " 当前项说明"
|
||||
if lang == "en" {
|
||||
panelTitle = " Current Item Description"
|
||||
}
|
||||
s.WriteString(tSectStyle.Render(panelTitle) + "\n")
|
||||
s.WriteString(tPanelStyle.Width(maxInt(60, m.width-6)).Render(m.currentCustomDescription(lang)) + "\n")
|
||||
|
||||
if m.editingText {
|
||||
if lang == "zh" {
|
||||
s.WriteString("\n" + tWarnStyle.Render(" 文本编辑模式: Enter 保存, Esc 取消") + "\n")
|
||||
} else {
|
||||
s.WriteString("\n" + tWarnStyle.Render(" Text edit mode: Enter save, Esc cancel") + "\n")
|
||||
}
|
||||
s.WriteString(" " + m.textInput.View() + "\n")
|
||||
}
|
||||
|
||||
s.WriteString("\n")
|
||||
if lang == "zh" {
|
||||
s.WriteString(tHelpStyle.Render(" ↑/↓ 移动 Enter/空格 切换 ←/→ 改选项 a 全选 Esc 返回 q 退出"))
|
||||
} else {
|
||||
s.WriteString(tHelpStyle.Render(" Up/Down Move Enter/Space Toggle Left/Right Cycle a All Esc Back q Quit"))
|
||||
}
|
||||
s.WriteString("\n")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func maxInt(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func RunTuiMenu(preCheck utils.NetCheckResult, config *params.Config) tuiResult {
|
||||
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
|
||||
}
|
||||
|
||||
func applyCustomResult(result tuiResult, preCheck utils.NetCheckResult, config *params.Config) {
|
||||
for _, t := range result.toggles {
|
||||
enabled := t.enabled
|
||||
if t.needNet && !preCheck.Connected {
|
||||
enabled = false
|
||||
}
|
||||
switch t.key {
|
||||
case "basic":
|
||||
config.BasicStatus = enabled
|
||||
case "cpu":
|
||||
config.CpuTestStatus = enabled
|
||||
case "memory":
|
||||
config.MemoryTestStatus = enabled
|
||||
case "disk":
|
||||
config.DiskTestStatus = enabled
|
||||
case "ut":
|
||||
config.UtTestStatus = enabled
|
||||
case "security":
|
||||
config.SecurityTestStatus = enabled
|
||||
case "email":
|
||||
config.EmailTestStatus = enabled
|
||||
case "backtrace":
|
||||
config.BacktraceStatus = enabled
|
||||
case "nt3":
|
||||
config.Nt3Status = enabled
|
||||
case "speed":
|
||||
config.SpeedTestStatus = enabled
|
||||
case "ping":
|
||||
config.PingTestStatus = enabled
|
||||
case "tgdc":
|
||||
config.TgdcTestStatus = enabled
|
||||
case "web":
|
||||
config.WebTestStatus = enabled
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range result.advanced {
|
||||
switch a.key {
|
||||
case "cpum":
|
||||
config.CpuTestMethod = a.options[a.current].value
|
||||
case "cput":
|
||||
config.CpuTestThreadMode = a.options[a.current].value
|
||||
case "memorym":
|
||||
config.MemoryTestMethod = a.options[a.current].value
|
||||
case "diskm":
|
||||
config.DiskTestMethod = a.options[a.current].value
|
||||
case "diskp":
|
||||
config.DiskTestPath = strings.TrimSpace(a.textVal)
|
||||
case "diskmc":
|
||||
config.DiskMultiCheck = a.boolVal
|
||||
case "nt3loc":
|
||||
config.Nt3Location = a.options[a.current].value
|
||||
case "nt3t":
|
||||
config.Nt3CheckType = a.options[a.current].value
|
||||
case "spnum":
|
||||
if v, err := strconv.Atoi(a.options[a.current].value); err == nil {
|
||||
config.SpNum = v
|
||||
}
|
||||
case "log":
|
||||
config.EnableLogger = a.boolVal
|
||||
case "upload":
|
||||
config.EnableUpload = a.boolVal
|
||||
case "analysis":
|
||||
config.AnalyzeResult = a.boolVal
|
||||
case "filepath":
|
||||
if strings.TrimSpace(a.textVal) != "" {
|
||||
config.FilePath = strings.TrimSpace(a.textVal)
|
||||
}
|
||||
case "width":
|
||||
if v, err := strconv.Atoi(a.options[a.current].value); err == nil {
|
||||
config.Width = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !config.BasicStatus && !config.CpuTestStatus && !config.MemoryTestStatus && !config.DiskTestStatus {
|
||||
config.OnlyIpInfoCheck = true
|
||||
}
|
||||
config.AutoChangeDiskMethod = true
|
||||
}
|
||||
485
internal/params/params.go
Normal file
485
internal/params/params.go
Normal file
@@ -0,0 +1,485 @@
|
||||
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
|
||||
AnalyzeResult 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,
|
||||
AnalyzeResult: false,
|
||||
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,
|
||||
"analysis": true, "analyze": 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.BoolVar(&c.AnalyzeResult, "analysis", false, "Enable/Disable post-test concise summary analysis")
|
||||
c.GoecsFlag.BoolVar(&c.AnalyzeResult, "analyze", false, "Enable/Disable post-test concise summary analysis")
|
||||
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
|
||||
}
|
||||
if c.UserSetFlags["analysis"] || c.UserSetFlags["analyze"] {
|
||||
saved["analysis"] = c.AnalyzeResult
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
if val, ok := saved["analysis"]; ok {
|
||||
if boolVal, ok := val.(bool); ok {
|
||||
c.AnalyzeResult = boolVal
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
555
internal/runner/runner.go
Normal file
555
internal/runner/runner.go
Normal file
@@ -0,0 +1,555 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/ecs/internal/analysis"
|
||||
"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.Choice == "6" || config.Choice == "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)
|
||||
} else {
|
||||
// Custom menu mode and any other fallback choices.
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "cu", config.SpNum, config.Language)
|
||||
tests.CustomSP("net", "ct", config.SpNum, config.Language)
|
||||
tests.CustomSP("net", "cmcc", config.SpNum, 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)
|
||||
}
|
||||
|
||||
// AppendAnalysisSummary appends a concise bilingual summary for easier interpretation.
|
||||
func AppendAnalysisSummary(config *params.Config, output, tempOutput string, outputMutex *sync.Mutex) string {
|
||||
outputMutex.Lock()
|
||||
defer outputMutex.Unlock()
|
||||
finalOutput := output
|
||||
return utils.PrintAndCapture(func() {
|
||||
summary := analysis.GenerateSummary(config, finalOutput)
|
||||
if strings.TrimSpace(summary) == "" {
|
||||
return
|
||||
}
|
||||
if config.Language == "zh" {
|
||||
utils.PrintCenteredTitle("测试总结分析", config.Width)
|
||||
} else {
|
||||
utils.PrintCenteredTitle("Result Summary Analysis", config.Width)
|
||||
}
|
||||
fmt.Println(summary)
|
||||
}, 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package cputest
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -8,6 +10,14 @@ import (
|
||||
)
|
||||
|
||||
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"
|
||||
@@ -1,6 +1,8 @@
|
||||
package disktest
|
||||
package tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -8,6 +10,14 @@ import (
|
||||
)
|
||||
|
||||
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)
|
||||
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,89 +0,0 @@
|
||||
package memorytest
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/memorytest/memory"
|
||||
)
|
||||
|
||||
func MemoryTest(language, testMethod string) (realTestMethod, res string) {
|
||||
testMethod = strings.ToLower(testMethod)
|
||||
if testMethod == "" {
|
||||
testMethod = "auto"
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
switch testMethod {
|
||||
case "stream":
|
||||
res = memory.WinsatTest(language)
|
||||
realTestMethod = "winsat"
|
||||
case "dd":
|
||||
res = memory.WindowsDDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res += memory.WinsatTest(language)
|
||||
realTestMethod = "winsat"
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
case "sysbench":
|
||||
res = memory.WinsatTest(language)
|
||||
realTestMethod = "winsat"
|
||||
case "auto", "winsat":
|
||||
res = memory.WinsatTest(language)
|
||||
realTestMethod = "winsat"
|
||||
default:
|
||||
res = memory.WinsatTest(language)
|
||||
realTestMethod = "winsat"
|
||||
}
|
||||
} else {
|
||||
switch testMethod {
|
||||
case "stream":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res += memory.DDTest(language)
|
||||
realTestMethod = "dd"
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "dd":
|
||||
res = memory.DDTest(language)
|
||||
realTestMethod = "dd"
|
||||
case "sysbench":
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res += memory.DDTest(language)
|
||||
realTestMethod = "dd"
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
case "auto":
|
||||
res = memory.StreamTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.DDTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
res = memory.SysBenchTest(language)
|
||||
if res == "" || strings.TrimSpace(res) == "" {
|
||||
realTestMethod = ""
|
||||
} else {
|
||||
realTestMethod = "sysbench"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "dd"
|
||||
}
|
||||
} else {
|
||||
realTestMethod = "stream"
|
||||
}
|
||||
case "winsat":
|
||||
// winsat 仅 Windows 支持,非 Windows fallback 到 dd
|
||||
res = memory.DDTest(language)
|
||||
realTestMethod = "dd"
|
||||
default:
|
||||
res = "Unsupported test method"
|
||||
realTestMethod = ""
|
||||
}
|
||||
}
|
||||
if !strings.Contains(res, "\n") && res != "" {
|
||||
res += "\n"
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package memorytest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
_, res := MemoryTest("zh", "stream")
|
||||
fmt.Print(res)
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package nexttrace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/oneclickvirt/nt3/nt"
|
||||
)
|
||||
|
||||
func NextTrace3Check(language, nt3Location, nt3CheckType string) {
|
||||
resultChan := make(chan nt.TraceResult, 100)
|
||||
go 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" {
|
||||
for _, res := range result.Output {
|
||||
res = strings.TrimSpace(res)
|
||||
if res != "" {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for _, res := range result.Output {
|
||||
res = strings.TrimSpace(res)
|
||||
if res == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(res, "ICMP") {
|
||||
fmt.Print(res)
|
||||
} else {
|
||||
fmt.Println(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package nexttrace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNextTrace3Check(t *testing.T) {
|
||||
start := time.Now()
|
||||
NextTrace3Check("zh", "ALL", "ipv4")
|
||||
duration := time.Since(start)
|
||||
t.Logf("执行耗时: %s", duration)
|
||||
}
|
||||
@@ -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, language string) {
|
||||
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, language)
|
||||
} else {
|
||||
sp.OfficialCustomSpeedTest(url, parseType, num, language)
|
||||
}
|
||||
}
|
||||
@@ -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("%s", MediaTest("zh"))
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package upstreams
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/oneclickvirt/UnlockTests/uts"
|
||||
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() {
|
||||
results := ConcurrentResults{}
|
||||
var wg sync.WaitGroup
|
||||
if IPV4 != "" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
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()
|
||||
result := backtrace.BackTrace(uts.IPV6)
|
||||
results.backtraceResult = result
|
||||
}()
|
||||
wg.Wait()
|
||||
if results.bgpResult != "" {
|
||||
fmt.Print(results.bgpResult)
|
||||
}
|
||||
if results.backtraceResult != "" {
|
||||
fmt.Printf("%s\n", results.backtraceResult)
|
||||
}
|
||||
fmt.Println(Yellow("准确线路自行查看详细路由,本测试结果仅作参考"))
|
||||
fmt.Println(Yellow("同一目标地址多个线路时,检测可能已越过汇聚层,除第一个线路外,后续信息可能无效"))
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package upstreams
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUpstreamsCheck(t *testing.T) {
|
||||
IPV4 = "148.100.85.25"
|
||||
UpstreamsCheck()
|
||||
}
|
||||
210
utils/utils.go
210
utils/utils.go
@@ -18,7 +18,7 @@ import (
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/oneclickvirt/UnlockTests/uts"
|
||||
"github.com/oneclickvirt/UnlockTests/executor"
|
||||
bnetwork "github.com/oneclickvirt/basics/network"
|
||||
"github.com/oneclickvirt/basics/system"
|
||||
butils "github.com/oneclickvirt/basics/utils"
|
||||
@@ -26,6 +26,88 @@ import (
|
||||
"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"`
|
||||
@@ -68,7 +150,7 @@ func PrintHead(language string, width int, ecsVersion string) {
|
||||
}
|
||||
}
|
||||
|
||||
func CheckChina(enableLogger bool) bool {
|
||||
func CheckChina(enableLogger bool, language string) bool {
|
||||
if enableLogger {
|
||||
InitLogger()
|
||||
defer Logger.Sync()
|
||||
@@ -84,7 +166,7 @@ func CheckChina(enableLogger bool) bool {
|
||||
ipapiResp, err := client.R().Get(ipapiURL)
|
||||
if err != nil {
|
||||
if enableLogger {
|
||||
Logger.Info("无法获取IP信息:" + err.Error())
|
||||
Logger.Info("Failed to get IP info: " + err.Error())
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -92,24 +174,41 @@ func CheckChina(enableLogger bool) bool {
|
||||
ipapiBody, err := ipapiResp.ToString()
|
||||
if err != nil {
|
||||
if enableLogger {
|
||||
Logger.Info("无法读取IP信息响应:" + err.Error())
|
||||
Logger.Info("Failed to read IP info response: " + err.Error())
|
||||
}
|
||||
return false
|
||||
}
|
||||
isInChina := strings.Contains(ipapiBody, "China")
|
||||
if isInChina {
|
||||
fmt.Println("根据 ipapi.co 提供的信息,当前IP可能在中国")
|
||||
var input string
|
||||
fmt.Print("是否选用中国专项测试(无流媒体测试,有三网Ping值测试)? ([y]/n) ")
|
||||
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":
|
||||
fmt.Println("使用中国专项测试")
|
||||
if language == "zh" {
|
||||
fmt.Println("使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Using China-specific test")
|
||||
}
|
||||
selectChina = true
|
||||
case "no", "n":
|
||||
fmt.Println("不使用中国专项测试")
|
||||
if language == "zh" {
|
||||
fmt.Println("不使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Not using China-specific test")
|
||||
}
|
||||
default:
|
||||
fmt.Println("使用中国专项测试")
|
||||
if language == "zh" {
|
||||
fmt.Println("使用中国专项测试")
|
||||
} else {
|
||||
fmt.Println("Using China-specific test")
|
||||
}
|
||||
selectChina = true
|
||||
}
|
||||
}
|
||||
@@ -124,14 +223,14 @@ func OnlyBasicsIpInfo(language string) (string, string, string) {
|
||||
}
|
||||
basicInfo := ipInfo
|
||||
if strings.Contains(ipInfo, "IPV4") && strings.Contains(ipInfo, "IPV6") && ipv4 != "" && ipv6 != "" {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = true
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = true
|
||||
} else if strings.Contains(ipInfo, "IPV4") && ipv4 != "" {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = false
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = false
|
||||
} else if strings.Contains(ipInfo, "IPV6") && ipv6 != "" {
|
||||
uts.IPV6 = true
|
||||
uts.IPV4 = false
|
||||
executor.IPV6 = true
|
||||
executor.IPV4 = false
|
||||
}
|
||||
basicInfo = strings.ReplaceAll(basicInfo, "\n\n", "\n")
|
||||
return ipv4, ipv6, basicInfo
|
||||
@@ -157,20 +256,20 @@ func BasicsAndSecurityCheck(language, nt3CheckType string, securityCheckStatus b
|
||||
wgt.Wait()
|
||||
basicInfo := systemInfo + ipInfo
|
||||
if strings.Contains(ipInfo, "IPV4") && strings.Contains(ipInfo, "IPV6") && ipv4 != "" && ipv6 != "" {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = true
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = true
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv4"
|
||||
}
|
||||
} else if strings.Contains(ipInfo, "IPV4") && ipv4 != "" {
|
||||
uts.IPV4 = true
|
||||
uts.IPV6 = false
|
||||
executor.IPV4 = true
|
||||
executor.IPV6 = false
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv4"
|
||||
}
|
||||
} else if strings.Contains(ipInfo, "IPV6") && ipv6 != "" {
|
||||
uts.IPV6 = true
|
||||
uts.IPV4 = false
|
||||
executor.IPV6 = true
|
||||
executor.IPV4 = false
|
||||
if nt3CheckType == "" {
|
||||
nt3CheckType = "ipv6"
|
||||
}
|
||||
@@ -203,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)
|
||||
@@ -228,12 +318,22 @@ 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()
|
||||
// 返回捕获的输出字符串
|
||||
// stderrBuf.String()
|
||||
return stdoutBuf.String()
|
||||
@@ -313,11 +413,11 @@ func UploadText(absPath string) (string, string, error) {
|
||||
}
|
||||
|
||||
// ProcessAndUpload 创建结果文件并上传文件
|
||||
func ProcessAndUpload(output string, filePath string, enableUplaod bool) (string, string) {
|
||||
func ProcessAndUpload(output string, filePath string, enableUplaod bool, language string) (string, string) {
|
||||
// 使用 defer 来处理 panic
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("处理上传时发生错误: %v\n", r)
|
||||
fmt.Fprintf(os.Stderr, "[ERROR] Fatal error during upload: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 检查文件是否存在
|
||||
@@ -325,14 +425,22 @@ func ProcessAndUpload(output string, filePath string, enableUplaod bool) (string
|
||||
// 文件存在,删除文件
|
||||
err = os.Remove(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法删除文件:", err)
|
||||
if language == "zh" {
|
||||
fmt.Println("无法删除文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to delete file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
// 创建文件
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法创建文件:", err)
|
||||
if language == "zh" {
|
||||
fmt.Println("无法创建文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to create file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
defer file.Close()
|
||||
@@ -344,27 +452,47 @@ func ProcessAndUpload(output string, filePath string, enableUplaod bool) (string
|
||||
writer := bufio.NewWriter(file)
|
||||
_, err = writer.WriteString(cleanedOutput)
|
||||
if err != nil {
|
||||
fmt.Println("无法写入文件:", err)
|
||||
if language == "zh" {
|
||||
fmt.Println("无法写入文件:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to write file:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
// 确保写入缓冲区的数据都刷新到文件中
|
||||
err = writer.Flush()
|
||||
if err != nil {
|
||||
fmt.Println("无法刷新文件缓冲:", err)
|
||||
if language == "zh" {
|
||||
fmt.Println("无法刷新文件缓冲:", err)
|
||||
} else {
|
||||
fmt.Println("Failed to flush file buffer:", err)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
fmt.Printf("测试结果已写入 %s\n", filePath)
|
||||
if language == "zh" {
|
||||
fmt.Printf("测试结果已写入 %s\n", filePath)
|
||||
} else {
|
||||
fmt.Printf("Test results written to %s\n", filePath)
|
||||
}
|
||||
if enableUplaod {
|
||||
// 获取文件的绝对路径
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("无法获取文件绝对路径:", err)
|
||||
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 {
|
||||
fmt.Println("上传失败,无法生成链接")
|
||||
if language == "zh" {
|
||||
fmt.Println("上传失败,无法生成链接")
|
||||
} else {
|
||||
fmt.Println("Upload failed, unable to generate link")
|
||||
}
|
||||
fmt.Println(err.Error())
|
||||
return "", ""
|
||||
}
|
||||
@@ -425,6 +553,8 @@ func CheckPublicAccess(timeout time.Duration) NetCheckResult {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user