阶段性提交

This commit is contained in:
刘祥超
2020-09-09 18:53:53 +08:00
parent fa7edac94b
commit be9fc79b92
115 changed files with 7910 additions and 744 deletions

51
build/build.sh Executable file
View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
function build() {
VERSION_DATA=$(cat ../internal/const/const.go)
re="Version[ ]+=[ ]+\"([0-9.]+)\""
if [[ $VERSION_DATA =~ $re ]]; then
VERSION=${BASH_REMATCH[1]}
else
echo "could not match version"
exit
fi
echo "checking ..."
ZIP_PATH=$(which zip)
if [ -z $ZIP_PATH ]; then
echo "we need 'zip' command to compress files"
exit
fi
echo "building v${VERSION}/${1}/${2} ..."
NAME="edge-node"
DIST="../dist/${NAME}"
ZIP="${NAME}-${1}-${2}-v${VERSION}.zip"
echo "copying ..."
if [ ! -d $DIST ]; then
mkdir $DIST
mkdir $DIST/bin
mkdir $DIST/configs
mkdir $DIST/logs
fi
cp configs/api.template.yaml $DIST/configs
cp -R www $DIST/
echo "building ..."
env GOOS=${1} GOARCH=${2} go build -o $DIST/bin/${NAME} -ldflags="-s -w" ../cmd/edge-node/main.go
echo "zip files"
cd "${DIST}/../" || exit
if [ -f "${ZIP}" ]; then
rm -f "${ZIP}"
fi
zip -r -X -q "${ZIP}" ${NAME}/
rm -rf ${NAME}
cd - || exit
echo "OK"
}
build "linux" "amd64"

8
build/config.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
TARGET=../../EdgeAdmin/internal/serverconfigs
if [ -d ${TARGET} ]
then
rm -rf ../../EdgeAdmin/internal/serverconfigs
fi
cp -R ../internal/configs/serverconfigs ../../EdgeAdmin/internal/configs/

1
build/configs/README.md Normal file
View File

@@ -0,0 +1 @@
* `global.yaml` - 全局配置

View File

@@ -1,4 +1,4 @@
rpc: rpc:
endpoints: [ "127.0.0.1:8003" ] endpoints: [ ${endpoints} ]
nodeId: "" nodeId: "${nodeId}"
secret: "" secret: "${nodeSecret}"

1
build/logs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.log

View File

@@ -1,11 +1,29 @@
package main package main
import ( import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/apps"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/nodes" "github.com/TeaOSLab/EdgeNode/internal/nodes"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
) )
func main() { func main() {
node := nodes.NewNode() app := apps.NewAppCmd().
node.Start() Version(teaconst.Version).
Product(teaconst.ProductName).
Usage(teaconst.ProcessName + " [-v|start|stop|restart|sync|update]")
app.On("sync", func() {
// TODO
fmt.Println("not implemented yet")
})
app.On("update", func() {
// TODO
fmt.Println("not implemented yet")
})
app.Run(func() {
node := nodes.NewNode()
node.Start()
})
} }

1
dist/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.zip

12
go.mod
View File

@@ -3,9 +3,19 @@ module github.com/TeaOSLab/EdgeNode
go 1.14 go 1.14
require ( require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-redis/redis v6.15.8+incompatible // indirect
github.com/go-yaml/yaml v2.1.0+incompatible github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/protobuf v1.4.2 github.com/golang/protobuf v1.4.2
github.com/iwind/TeaGo v0.0.0-20200722010955-47dd648dc761 github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect
github.com/shirou/gopsutil v2.20.7+incompatible
github.com/stretchr/testify v1.6.1 // indirect
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7
google.golang.org/grpc v1.30.0 google.golang.org/grpc v1.30.0
google.golang.org/protobuf v1.25.0 google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
) )

68
go.sum
View File

@@ -1,15 +1,30 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
@@ -33,34 +48,71 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iwind/TeaGo v0.0.0-20200722010955-47dd648dc761 h1:70Iaf6iVF4CiH1P5Au3hqile/2Ea0XIkR4SPpdiaKI0= github.com/iwind/TeaGo v0.0.0-20200727075925-7e7e67b44f2d h1:V7HA0wUOdmZbXJTVpiUEvSD4ARKHwMLMmiCccfkqf24=
github.com/iwind/TeaGo v0.0.0-20200722010955-47dd648dc761/go.mod h1:zjM7k+b+Jthhf0T0fKwuF0iy4TWb5SsU1gmKR2l+OmE= github.com/iwind/TeaGo v0.0.0-20200727075925-7e7e67b44f2d/go.mod h1:zjM7k+b+Jthhf0T0fKwuF0iy4TWb5SsU1gmKR2l+OmE=
github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a h1:VaWcMNOzHHT1y8MeTA2fWhG6GEfAdy6CwF2tW+KiY5Y=
github.com/iwind/TeaGo v0.0.0-20200822074248-b1cf7248c98a/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w=
github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7 h1:AeiKBIuRw3UomYXSbLy0Mc2dDLfdtbT/IVn4keq83P0=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -71,8 +123,12 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
@@ -86,12 +142,15 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@@ -111,12 +170,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

234
internal/apps/app_cmd.go Normal file
View File

@@ -0,0 +1,234 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"os"
"os/exec"
"runtime"
"strconv"
"time"
)
// App命令帮助
type AppCmd struct {
product string
version string
usage string
options []*CommandHelpOption
appendStrings []string
directives []*Directive
}
func NewAppCmd() *AppCmd {
return &AppCmd{}
}
type CommandHelpOption struct {
Code string
Description string
}
// 产品
func (this *AppCmd) Product(product string) *AppCmd {
this.product = product
return this
}
// 版本
func (this *AppCmd) Version(version string) *AppCmd {
this.version = version
return this
}
// 使用方法
func (this *AppCmd) Usage(usage string) *AppCmd {
this.usage = usage
return this
}
// 选项
func (this *AppCmd) Option(code string, description string) *AppCmd {
this.options = append(this.options, &CommandHelpOption{
Code: code,
Description: description,
})
return this
}
// 附加内容
func (this *AppCmd) Append(appendString string) *AppCmd {
this.appendStrings = append(this.appendStrings, appendString)
return this
}
// 打印
func (this *AppCmd) Print() {
fmt.Println(this.product + " v" + this.version)
usage := this.usage
fmt.Println("Usage:", "\n "+usage)
if len(this.options) > 0 {
fmt.Println("")
fmt.Println("Options:")
spaces := 20
max := 40
for _, option := range this.options {
l := len(option.Code)
if l < max && l > spaces {
spaces = l + 4
}
}
for _, option := range this.options {
if len(option.Code) > max {
fmt.Println("")
fmt.Println(" " + option.Code)
option.Code = ""
}
fmt.Printf(" %-"+strconv.Itoa(spaces)+"s%s\n", option.Code, ": "+option.Description)
}
}
if len(this.appendStrings) > 0 {
fmt.Println("")
for _, s := range this.appendStrings {
fmt.Println(s)
}
}
}
// 添加指令
func (this *AppCmd) On(arg string, callback func()) {
this.directives = append(this.directives, &Directive{
Arg: arg,
Callback: callback,
})
}
// 运行
func (this *AppCmd) Run(main func()) {
// 获取参数
args := os.Args[1:]
if len(args) > 0 {
switch args[0] {
case "-v", "version", "-version", "--version":
this.runVersion()
return
case "?", "help", "-help", "h", "-h":
this.runHelp()
return
case "start":
this.runStart()
return
case "stop":
this.runStop()
return
case "restart":
this.runRestart()
return
case "status":
this.runStatus()
return
}
// 查找指令
for _, directive := range this.directives {
if directive.Arg == args[0] {
directive.Callback()
return
}
}
fmt.Println("unknown command '" + args[0] + "'")
return
}
// 记录PID
_ = this.writePid()
// 日志
writer := new(LogWriter)
writer.Init()
logs.SetWriter(writer)
// 运行主函数
main()
}
// 版本号
func (this *AppCmd) runVersion() {
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
}
// 帮助
func (this *AppCmd) runHelp() {
this.Print()
}
// 启动
func (this *AppCmd) runStart() {
proc := this.checkPid()
if proc != nil {
fmt.Println(this.product+" already started, pid:", proc.Pid)
return
}
cmd := exec.Command(os.Args[0])
err := cmd.Start()
if err != nil {
fmt.Println(this.product+" start failed:", err.Error())
return
}
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
}
// 停止
func (this *AppCmd) runStop() {
proc := this.checkPid()
if proc == nil {
fmt.Println(this.product + " not started yet")
return
}
// 停止进程
_ = proc.Kill()
// 在Windows上经常不能及时释放资源
_ = DeletePid(Tea.Root + "/bin/pid")
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
}
// 重启
func (this *AppCmd) runRestart() {
this.runStop()
time.Sleep(1 * time.Second)
this.runStart()
}
// 状态
func (this *AppCmd) runStatus() {
proc := this.checkPid()
if proc == nil {
fmt.Println(this.product + " not started yet")
} else {
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
}
}
// 检查PID
func (this *AppCmd) checkPid() *os.Process {
return CheckPid(Tea.Root + "/bin/pid")
}
// 写入PID
func (this *AppCmd) writePid() error {
return WritePid(Tea.Root + "/bin/pid")
}

View File

@@ -0,0 +1,6 @@
package apps
type Directive struct {
Arg string
Callback func()
}

View File

@@ -0,0 +1,17 @@
// +build !windows
package apps
import (
"os"
"syscall"
)
// lock file
func LockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}
func UnlockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
}

View File

@@ -0,0 +1,17 @@
// +build windows
package apps
import (
"errors"
"os"
)
// lock file
func LockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}
func UnlockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}

View File

@@ -0,0 +1,51 @@
package apps
import (
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/time"
"log"
)
type LogWriter struct {
fileAppender *files.Appender
}
func (this *LogWriter) Init() {
// 创建目录
dir := files.NewFile(Tea.LogDir())
if !dir.Exists() {
err := dir.Mkdir()
if err != nil {
log.Println("[error]" + err.Error())
}
}
logFile := files.NewFile(Tea.LogFile("run.log"))
// 打开要写入的日志文件
appender, err := logFile.Appender()
if err != nil {
logs.Error(err)
} else {
this.fileAppender = appender
}
}
func (this *LogWriter) Write(message string) {
log.Println(message)
if this.fileAppender != nil {
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[error]" + err.Error())
}
}
}
func (this *LogWriter) Close() {
if this.fileAppender != nil {
_ = this.fileAppender.Close()
}
}

113
internal/apps/pid.go Normal file
View File

@@ -0,0 +1,113 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"runtime"
)
var pidFileList = []*os.File{}
// 检查Pid
func CheckPid(path string) *os.Process {
// windows上打开的文件是不能删除的
if runtime.GOOS == "windows" {
if os.Remove(path) == nil {
return nil
}
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
// 是否能取得Lock
err = LockFile(file)
if err == nil {
_ = UnlockFile(file)
return nil
}
pidBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil
}
pid := types.Int(string(pidBytes))
if pid <= 0 {
return nil
}
proc, _ := os.FindProcess(pid)
return proc
}
// 写入Pid
func WritePid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
if err != nil {
return err
}
return nil
}
// 写入Ppid
func WritePpid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
if err != nil {
return err
}
return nil
}
// 删除Pid
func DeletePid(path string) error {
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil
}
return err
}
for _, fp := range pidFileList {
_ = UnlockFile(fp)
_ = fp.Close()
}
return os.Remove(path)
}

View File

@@ -10,6 +10,8 @@ type APIConfig struct {
RPC struct { RPC struct {
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
} `yaml:"rpc"` } `yaml:"rpc"`
NodeId string `yaml:"nodeId"`
Secret string `yaml:"secret"`
} }
func LoadAPIConfig() (*APIConfig, error) { func LoadAPIConfig() (*APIConfig, error) {

View File

@@ -1,6 +1,7 @@
package configs package configs
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"github.com/go-yaml/yaml" "github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"io/ioutil" "io/ioutil"
@@ -9,10 +10,13 @@ import (
var sharedNodeConfig *NodeConfig = nil var sharedNodeConfig *NodeConfig = nil
type NodeConfig struct { type NodeConfig struct {
Id string `yaml:"id"` Id string `yaml:"id" json:"id"`
Servers []*ServerConfig `yaml:"servers"` IsOn bool `yaml:"isOn" json:"isOn"`
Servers []*serverconfigs.ServerConfig `yaml:"servers" json:"servers"`
Version int `yaml:"version" json:"version"`
} }
// 取得当前节点配置单例
func SharedNodeConfig() (*NodeConfig, error) { func SharedNodeConfig() (*NodeConfig, error) {
sharedLocker.Lock() sharedLocker.Lock()
defer sharedLocker.Unlock() defer sharedLocker.Unlock()
@@ -36,9 +40,19 @@ func SharedNodeConfig() (*NodeConfig, error) {
return config, nil return config, nil
} }
// 刷新当前节点配置
func ReloadNodeConfig() error {
sharedLocker.Lock()
sharedNodeConfig = nil
sharedLocker.Unlock()
_, err := SharedNodeConfig()
return err
}
// 根据网络地址和协议分组 // 根据网络地址和协议分组
func (this *NodeConfig) AvailableGroups() []*ServerGroup { func (this *NodeConfig) AvailableGroups() []*serverconfigs.ServerGroup {
groupMapping := map[string]*ServerGroup{} // protocol://addr => Server Group groupMapping := map[string]*serverconfigs.ServerGroup{} // protocol://addr => Server Group
for _, server := range this.Servers { for _, server := range this.Servers {
if !server.IsOn { if !server.IsOn {
continue continue
@@ -48,15 +62,39 @@ func (this *NodeConfig) AvailableGroups() []*ServerGroup {
if ok { if ok {
group.Add(server) group.Add(server)
} else { } else {
group = NewServerGroup(addr) group = serverconfigs.NewServerGroup(addr)
group.Add(server) group.Add(server)
} }
groupMapping[addr] = group groupMapping[addr] = group
} }
} }
result := []*ServerGroup{} result := []*serverconfigs.ServerGroup{}
for _, group := range groupMapping { for _, group := range groupMapping {
result = append(result, group) result = append(result, group)
} }
return result return result
} }
func (this *NodeConfig) Init() error {
for _, server := range this.Servers {
err := server.Init()
if err != nil {
return err
}
}
return nil
}
// 写入到文件
func (this *NodeConfig) Save() error {
sharedLocker.Lock()
defer sharedLocker.Unlock()
data, err := yaml.Marshal(this)
if err != nil {
return err
}
return ioutil.WriteFile(Tea.ConfigFile("node.yaml"), data, 0777)
}

View File

@@ -1,6 +1,7 @@
package configs package configs
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"testing" "testing"
@@ -27,18 +28,37 @@ func TestSharedNodeConfig(t *testing.T) {
func TestNodeConfig_Groups(t *testing.T) { func TestNodeConfig_Groups(t *testing.T) {
config := &NodeConfig{} config := &NodeConfig{}
config.Servers = []*ServerConfig{ config.Servers = []*serverconfigs.ServerConfig{
{ {
IsOn: true, IsOn: true,
HTTP: &HTTPProtocolConfig{ HTTP: &serverconfigs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: serverconfigs.BaseProtocol{
Listen: []string{"127.0.0.1:1234", ":8080"}, IsOn: true,
Listen: []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTP,
Host: "127.0.0.1",
PortRange: "1234",
},
{
Protocol: serverconfigs.ProtocolHTTP,
PortRange: "8080",
},
},
},
}, },
}, },
{ {
HTTP: &HTTPProtocolConfig{ HTTP: &serverconfigs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: serverconfigs.BaseProtocol{
Listen: []string{":8080"}, IsOn: true,
Listen: []*serverconfigs.NetworkAddressConfig{
{
Protocol: serverconfigs.ProtocolHTTP,
PortRange: "8080",
},
},
},
}, },
}, },
} }

View File

@@ -1,4 +0,0 @@
package configs
type OriginServerConfig struct {
}

View File

@@ -1,22 +0,0 @@
package configs
type HTTPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion IPVersion `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *HTTPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolHTTP4+"://"+listen)
case IPv6:
result = append(result, ProtocolHTTP6+"://"+listen)
default:
result = append(result, ProtocolHTTP+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type HTTPSProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion string `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *HTTPSProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolHTTPS4+"://"+listen)
case IPv6:
result = append(result, ProtocolHTTPS6+"://"+listen)
default:
result = append(result, ProtocolHTTPS+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type TCPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion IPVersion `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *TCPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolTCP4+"://"+listen)
case IPv6:
result = append(result, ProtocolTCP6+"://"+listen)
default:
result = append(result, ProtocolTCP+"://"+listen)
}
}
return result
}

View File

@@ -1,22 +0,0 @@
package configs
type TLSProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
IPVersion string `yaml:"ipVersion"` // 4, 6
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *TLSProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
switch this.IPVersion {
case IPv4:
result = append(result, ProtocolTLS4+"://"+listen)
case IPv6:
result = append(result, ProtocolTLS6+"://"+listen)
default:
result = append(result, ProtocolTLS+"://"+listen)
}
}
return result
}

View File

@@ -1,14 +0,0 @@
package configs
type UDPProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *UDPProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
result = append(result, ProtocolUDP+"://"+listen)
}
return result
}

View File

@@ -1,14 +0,0 @@
package configs
type UnixProtocolConfig struct {
IsOn bool `yaml:"isOn"` // 是否开启
Listen []string `yaml:"listen" json:"listen"` // 监听地址
}
func (this *UnixProtocolConfig) Addresses() []string {
result := []string{}
for _, listen := range this.Listen {
result = append(result, ProtocolUnix+":"+listen)
}
return result
}

View File

@@ -1,54 +0,0 @@
package configs
type ServerConfig struct {
Id string `yaml:"id"` // ID
IsOn bool `yaml:"isOn"` // 是否开启
Components []*ComponentConfig `yaml:"components"` // 组件
Filters []*FilterConfig `yaml:"filters"` // 过滤器
Name string `yaml:"name"` // 名称
Description string `yaml:"description"` // 描述
ServerNames []string `yaml:"serverNames"` // 域名
// 协议
HTTP *HTTPProtocolConfig `yaml:"http"` // HTTP配置
HTTPS *HTTPSProtocolConfig `yaml:"https"` // HTTPS配置
TCP *TCPProtocolConfig `yaml:"tcp"` // TCP配置
TLS *TLSProtocolConfig `yaml:"tls"` // TLS配置
Unix *UnixProtocolConfig `yaml:"unix"` // Unix配置
UDP *UDPProtocolConfig `yaml:"udp"` // UDP配置
// Web配置
Web *WebConfig `yaml:"web"`
}
func NewServerConfig() *ServerConfig {
return &ServerConfig{}
}
func (this *ServerConfig) Init() error {
return nil
}
func (this *ServerConfig) FullAddresses() []string {
result := []Protocol{}
if this.HTTP != nil && this.HTTP.IsOn {
result = append(result, this.HTTP.Addresses()...)
}
if this.HTTPS != nil && this.HTTPS.IsOn {
result = append(result, this.HTTPS.Addresses()...)
}
if this.TCP != nil && this.TCP.IsOn {
result = append(result, this.TCP.Addresses()...)
}
if this.TLS != nil && this.TLS.IsOn {
result = append(result, this.TLS.Addresses()...)
}
if this.Unix != nil && this.Unix.IsOn {
result = append(result, this.Unix.Addresses()...)
}
if this.UDP != nil && this.UDP.IsOn {
result = append(result, this.UDP.Addresses()...)
}
return result
}

View File

@@ -1,21 +0,0 @@
package configs
import "testing"
func TestServerConfig_Protocols(t *testing.T) {
{
server := NewServerConfig()
t.Log(server.FullAddresses())
}
{
server := NewServerConfig()
server.HTTP = &HTTPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.HTTPS = &HTTPSProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.TCP = &TCPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.TLS = &TLSProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.Unix = &UnixProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
server.UDP = &UDPProtocolConfig{IsOn: true, Listen: []string{"127.0.0.1:1234"}}
t.Log(server.FullAddresses())
}
}

View File

@@ -1,41 +0,0 @@
package configs
import "strings"
type ServerGroup struct {
fullAddr string
Servers []*ServerConfig
}
func NewServerGroup(fullAddr string) *ServerGroup {
return &ServerGroup{fullAddr: fullAddr}
}
// 添加服务
func (this *ServerGroup) Add(server *ServerConfig) {
this.Servers = append(this.Servers, server)
}
// 获取完整的地址
func (this *ServerGroup) FullAddr() string {
return this.fullAddr
}
// 获取当前分组的协议
func (this *ServerGroup) Protocol() Protocol {
for _, p := range AllProtocols() {
if strings.HasPrefix(this.fullAddr, p+":") {
return p
}
}
return ProtocolHTTP
}
// 获取当前分组的地址
func (this *ServerGroup) Addr() string {
protocol := this.Protocol()
if protocol == ProtocolUnix {
return strings.TrimPrefix(this.fullAddr, protocol+":")
}
return strings.TrimPrefix(this.fullAddr, protocol+"://")
}

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
type ComponentConfig struct { type ComponentConfig struct {
} }

View File

@@ -0,0 +1,20 @@
package configutils
import (
"reflect"
)
// 拷贝同类型struct指针对象中的字段
func CopyStructObject(destPtr, sourcePtr interface{}) {
value := reflect.ValueOf(destPtr)
value2 := reflect.ValueOf(sourcePtr)
countFields := value2.Elem().NumField()
for i := 0; i < countFields; i++ {
v := value2.Elem().Field(i)
if !v.IsValid() || !v.CanSet() {
continue
}
value.Elem().Field(i).Set(v)
}
}

View File

@@ -0,0 +1,28 @@
package configutils
import (
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestCopyStructObject(t *testing.T) {
type Book struct {
Name string
Price int
Year int
Author string
press string
}
book1 := &Book{
Name: "Hello Golang",
Price: 100,
Year: 2020,
Author: "Liu",
press: "Beijing",
}
book2 := new(Book)
CopyStructObject(book2, book1)
logs.PrintAsJSON(book2, t)
logs.PrintAsJSON(book1, t)
}

View File

@@ -0,0 +1,59 @@
package configutils
import (
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/string"
"strings"
)
// 从一组规则中匹配域名
// 支持的格式example.com, www.example.com, .example.com, *.example.com, ~(\d+).example.com
// 更多参考http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name
func MatchDomains(patterns []string, domain string) (isMatched bool) {
if len(patterns) == 0 {
return
}
for _, pattern := range patterns {
if matchDomain(pattern, domain) {
return true
}
}
return
}
// 匹配单个域名规则
func matchDomain(pattern string, domain string) (isMatched bool) {
if len(pattern) == 0 {
return
}
// 正则表达式
if pattern[0] == '~' {
reg, err := stringutil.RegexpCompile(strings.TrimSpace(pattern[1:]))
if err != nil {
logs.Error(err)
return false
}
return reg.MatchString(domain)
}
if pattern[0] == '.' {
return strings.HasSuffix(domain, pattern)
}
// 其他匹配
patternPieces := strings.Split(pattern, ".")
domainPieces := strings.Split(domain, ".")
if len(patternPieces) != len(domainPieces) {
return
}
isMatched = true
for index, patternPiece := range patternPieces {
if patternPiece == "" || patternPiece == "*" || patternPiece == domainPieces[index] {
continue
}
isMatched = false
break
}
return isMatched
}

View File

@@ -0,0 +1,79 @@
package configutils
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestMatchDomain(t *testing.T) {
a := assert.NewAssertion(t)
{
ok := MatchDomains([]string{}, "example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"example.com"}, "example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"www.example.com"}, "example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "a.www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{".example.com"}, "a.www.example123.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"*.example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"*.*.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"www.*.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"gallery.*.com"}, "www.example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"~\\w+.example.com"}, "www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"~\\w+.example.com"}, "a.www.example.com")
a.IsTrue(ok)
}
{
ok := MatchDomains([]string{"~^\\d+.example.com$"}, "www.example.com")
a.IsFalse(ok)
}
{
ok := MatchDomains([]string{"~^\\d+.example.com$"}, "123.example.com")
a.IsTrue(ok)
}
}

View File

@@ -0,0 +1,11 @@
package configutils
import "github.com/iwind/TeaGo/logs"
// 记录错误
func LogError(arg ...interface{}) {
if len(arg) == 0 {
return
}
logs.Println(arg...)
}

View File

@@ -0,0 +1,25 @@
package configutils
import (
"regexp"
"strings"
)
var whitespaceReg = regexp.MustCompile(`\s+`)
// 关键词匹配
func MatchKeyword(source, keyword string) bool {
if len(keyword) == 0 {
return false
}
pieces := whitespaceReg.Split(keyword, -1)
source = strings.ToLower(source)
for _, piece := range pieces {
if strings.Index(source, strings.ToLower(piece)) > -1 {
return true
}
}
return false
}

View File

@@ -0,0 +1,13 @@
package configutils
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestMatchKeyword(t *testing.T) {
a := assert.NewAssertion(t)
a.IsTrue(MatchKeyword("a b c", "a"))
a.IsFalse(MatchKeyword("a b c", ""))
a.IsTrue(MatchKeyword("abc", "BC"))
}

View File

@@ -0,0 +1,14 @@
package configutils
import (
"github.com/go-yaml/yaml"
"io/ioutil"
)
func UnmarshalYamlFile(file string, ptr interface{}) error {
data, err := ioutil.ReadFile(file)
if err != nil {
return err
}
return yaml.Unmarshal(data, ptr)
}

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
type FilterConfig struct { type FilterConfig struct {
} }

View File

@@ -0,0 +1,43 @@
package serverconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/shared"
)
var globalConfig *GlobalConfig = nil
var globalConfigFile = "global.yaml"
// 全局设置
type GlobalConfig struct {
HTTPAll struct {
MatchDomainStrictly bool `yaml:"matchDomainStrictly"`
} `yaml:"httpAll"`
HTTP struct{} `yaml:"http"`
HTTPS struct{} `yaml:"https"`
TCPAll struct{} `yaml:"tcpAll"`
TCP struct{} `yaml:"tcp"`
TLS struct{} `yaml:"tls"`
Unix struct{} `yaml:"unix"`
UDP struct{} `yaml:"udp"`
}
func SharedGlobalConfig() *GlobalConfig {
shared.Locker.Lock()
defer shared.Locker.Unlock()
if globalConfig != nil {
return globalConfig
}
err := configutils.UnmarshalYamlFile(globalConfigFile, globalConfig)
if err != nil {
configutils.LogError("[SharedGlobalConfig]" + err.Error())
globalConfig = &GlobalConfig{}
}
return globalConfig
}
func (this *GlobalConfig) Init() error {
return nil
}

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
type IPVersion = string type IPVersion = string

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
type LocationConfig struct { type LocationConfig struct {
} }

View File

@@ -0,0 +1,70 @@
package serverconfigs
import (
"github.com/iwind/TeaGo/types"
"regexp"
"strconv"
"strings"
)
var regexpSinglePort = regexp.MustCompile(`^\d+$`)
// 网络地址配置
type NetworkAddressConfig struct {
Protocol string `yaml:"protocol" json:"protocol"` // 协议http、tcp、tcp4、tcp6、unix、udp等
Host string `yaml:"host" json:"host"` // 主机地址或主机名
PortRange string `yaml:"portRange" json:"portRange"` // 端口范围,支持 8080、8080-8090、8080:8090
minPort int
maxPort int
}
func (this *NetworkAddressConfig) Init() error {
// 8080
if regexpSinglePort.MatchString(this.PortRange) {
this.minPort = types.Int(this.PortRange)
this.maxPort = this.minPort
return nil
}
// 8080:8090
if strings.Contains(this.PortRange, ":") {
pieces := strings.SplitN(this.PortRange, ":", 2)
minPort := types.Int(pieces[0])
maxPort := types.Int(pieces[1])
if minPort > maxPort {
minPort, maxPort = maxPort, minPort
}
this.minPort = minPort
this.maxPort = maxPort
return nil
}
// 8080-8090
if strings.Contains(this.PortRange, "-") {
pieces := strings.SplitN(this.PortRange, "-", 2)
minPort := types.Int(pieces[0])
maxPort := types.Int(pieces[1])
if minPort > maxPort {
minPort, maxPort = maxPort, minPort
}
this.minPort = minPort
this.maxPort = maxPort
return nil
}
return nil
}
func (this *NetworkAddressConfig) FullAddresses() []string {
if this.Protocol == ProtocolUnix {
return []string{this.Protocol + ":" + this.Host}
}
result := []string{}
for i := this.minPort; i <= this.maxPort; i++ {
host := this.Host
result = append(result, this.Protocol+"://"+host+":"+strconv.Itoa(i))
}
return result
}

View File

@@ -0,0 +1,57 @@
package serverconfigs
import "testing"
func TestNetworkAddressConfig_FullAddresses(t *testing.T) {
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080:8090",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080-8090",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
{
addr := &NetworkAddressConfig{
Protocol: "http",
Host: "127.0.0.1",
PortRange: "8080-8070",
}
err := addr.Init()
if err != nil {
t.Fatal(err)
}
t.Log(addr.FullAddresses())
}
}

View File

@@ -0,0 +1,10 @@
package serverconfigs
// 源站服务配置
type OriginServerConfig struct {
Id string `yaml:"id" json:"id"` // ID
IsOn bool `yaml:"isOn" json:"isOn"` // 是否启用
Name string `yaml:"name" json:"name"` // 名称 TODO
Addr *NetworkAddressConfig `yaml:"addr" json:"addr"` // 地址
Description string `yaml:"description" json:"description"` // 描述 TODO
}

View File

@@ -0,0 +1,6 @@
package serverconfigs
// TODO 需要实现
type OriginServerGroupConfig struct {
Origins []*OriginServerConfig `yaml:"origins" json:"origins"` // 源站列表
}

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
type Protocol = string type Protocol = string

View File

@@ -0,0 +1,32 @@
package serverconfigs
// 协议基础数据结构
type BaseProtocol struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Listen []*NetworkAddressConfig `yaml:"listen" json:"listen"` // 绑定的网络地址
}
// 初始化
func (this *BaseProtocol) InitBase() error {
for _, addr := range this.Listen {
err := addr.Init()
if err != nil {
return err
}
}
return nil
}
// 获取完整的地址列表
func (this *BaseProtocol) FullAddresses() []string {
result := []string{}
for _, addr := range this.Listen {
result = append(result, addr.FullAddresses()...)
}
return result
}
// 添加地址
func (this *BaseProtocol) AddListen(addr ...*NetworkAddressConfig) {
this.Listen = append(this.Listen, addr...)
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type HTTPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *HTTPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,24 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
// TLS Version
type TLSVersion = string
// Cipher Suites
type TLSCipherSuite = string
type HTTPSProtocolConfig struct {
BaseProtocol `yaml:",inline"`
SSL *sslconfigs.SSLConfig `yaml:"ssl"`
}
func (this *HTTPSProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type TCPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *TCPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,18 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
type TLSProtocolConfig struct {
BaseProtocol `yaml:",inline"`
SSL *sslconfigs.SSLConfig `yaml:"ssl"`
}
func (this *TLSProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type UDPProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *UDPProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,14 @@
package serverconfigs
type UnixProtocolConfig struct {
BaseProtocol `yaml:",inline"`
}
func (this *UnixProtocolConfig) Init() error {
err := this.InitBase()
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,6 @@
package serverconfigs
type ReverseProxyConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否启用
Origins []*OriginServerConfig `yaml:"origins" json:"origins"` // 源站列表
}

View File

@@ -0,0 +1,178 @@
package serverconfigs
import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
)
type ServerConfig struct {
Id string `yaml:"id" json:"id"` // ID
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Components []*ComponentConfig `yaml:"components" json:"components"` // 组件
Filters []*FilterConfig `yaml:"filters" json:"filters"` // 过滤器
Name string `yaml:"name" json:"name"` // 名称
Description string `yaml:"description" json:"description"` // 描述
ServerNames []*ServerNameConfig `yaml:"serverNames" json:"serverNames"` // 域名
// 前端协议
HTTP *HTTPProtocolConfig `yaml:"http" json:"http"` // HTTP配置
HTTPS *HTTPSProtocolConfig `yaml:"https" json:"https"` // HTTPS配置
TCP *TCPProtocolConfig `yaml:"tcp" json:"tcp"` // TCP配置
TLS *TLSProtocolConfig `yaml:"tls" json:"tls"` // TLS配置
Unix *UnixProtocolConfig `yaml:"unix" json:"unix"` // Unix配置
UDP *UDPProtocolConfig `yaml:"udp" json:"udp"` // UDP配置
// Web配置
Web *WebConfig `yaml:"web" json:"web"`
// 反向代理配置
ReverseProxy *ReverseProxyConfig `yaml:"reverseProxy" json:"reverseProxy"`
}
func NewServerConfig() *ServerConfig {
return &ServerConfig{}
}
func (this *ServerConfig) Init() error {
if this.HTTP != nil {
err := this.HTTP.Init()
if err != nil {
return err
}
}
if this.HTTPS != nil {
err := this.HTTPS.Init()
if err != nil {
return err
}
}
if this.TCP != nil {
err := this.TCP.Init()
if err != nil {
return err
}
}
if this.TLS != nil {
err := this.TLS.Init()
if err != nil {
return err
}
}
if this.Unix != nil {
err := this.Unix.Init()
if err != nil {
return err
}
}
if this.UDP != nil {
err := this.UDP.Init()
if err != nil {
return err
}
}
return nil
}
func (this *ServerConfig) FullAddresses() []string {
result := []Protocol{}
if this.HTTP != nil && this.HTTP.IsOn {
result = append(result, this.HTTP.FullAddresses()...)
}
if this.HTTPS != nil && this.HTTPS.IsOn {
result = append(result, this.HTTPS.FullAddresses()...)
}
if this.TCP != nil && this.TCP.IsOn {
result = append(result, this.TCP.FullAddresses()...)
}
if this.TLS != nil && this.TLS.IsOn {
result = append(result, this.TLS.FullAddresses()...)
}
if this.Unix != nil && this.Unix.IsOn {
result = append(result, this.Unix.FullAddresses()...)
}
if this.UDP != nil && this.UDP.IsOn {
result = append(result, this.UDP.FullAddresses()...)
}
return result
}
func (this *ServerConfig) Listen() []*NetworkAddressConfig {
result := []*NetworkAddressConfig{}
if this.HTTP != nil {
result = append(result, this.HTTP.Listen...)
}
if this.HTTPS != nil {
result = append(result, this.HTTPS.Listen...)
}
if this.TCP != nil {
result = append(result, this.TCP.Listen...)
}
if this.TLS != nil {
result = append(result, this.TLS.Listen...)
}
if this.Unix != nil {
result = append(result, this.Unix.Listen...)
}
if this.UDP != nil {
result = append(result, this.UDP.Listen...)
}
return result
}
func (this *ServerConfig) AsJSON() ([]byte, error) {
return json.Marshal(this)
}
func (this *ServerConfig) IsHTTP() bool {
return this.HTTP != nil || this.HTTPS != nil
}
func (this *ServerConfig) IsTCP() bool {
return this.TCP != nil || this.TLS != nil
}
func (this *ServerConfig) IsUnix() bool {
return this.Unix != nil
}
func (this *ServerConfig) IsUDP() bool {
return this.UDP != nil
}
// 判断是否和域名匹配
func (this *ServerConfig) MatchName(name string) bool {
for _, serverName := range this.ServerNames {
if serverName.Match(name) {
return true
}
}
return false
}
// 判断是否严格匹配
func (this *ServerConfig) MatchNameStrictly(name string) bool {
for _, serverName := range this.ServerNames {
if serverName.Name == name {
return true
}
}
return false
}
// SSL信息
func (this *ServerConfig) SSLConfig() *sslconfigs.SSLConfig {
if this.HTTPS != nil {
return this.HTTPS.SSL
}
if this.TLS != nil {
return this.TLS.SSL
}
return nil
}

View File

@@ -0,0 +1,74 @@
package serverconfigs
import "testing"
func TestServerConfig_Protocols(t *testing.T) {
{
server := NewServerConfig()
t.Log(server.FullAddresses())
}
{
server := NewServerConfig()
server.HTTP = &HTTPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolHTTP,
PortRange: "1234",
},
},
}}
server.HTTPS = &HTTPSProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolUnix,
Host: "/hello.sock",
PortRange: "1235",
},
},
}}
server.TCP = &TCPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolHTTPS,
PortRange: "1236",
},
},
}}
server.TLS = &TLSProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolTCP,
PortRange: "1234",
},
},
}}
server.Unix = &UnixProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolTLS,
PortRange: "1234",
},
},
}}
server.UDP = &UDPProtocolConfig{BaseProtocol: BaseProtocol{
IsOn: true,
Listen: []*NetworkAddressConfig{
{
Protocol: ProtocolUDP,
PortRange: "1234",
},
},
}}
err := server.Init()
if err != nil {
t.Fatal(err)
}
t.Log(server.FullAddresses())
}
}

View File

@@ -0,0 +1,85 @@
package serverconfigs
import "strings"
type ServerGroup struct {
fullAddr string
Servers []*ServerConfig
}
func NewServerGroup(fullAddr string) *ServerGroup {
return &ServerGroup{fullAddr: fullAddr}
}
// 添加服务
func (this *ServerGroup) Add(server *ServerConfig) {
this.Servers = append(this.Servers, server)
}
// 获取完整的地址
func (this *ServerGroup) FullAddr() string {
return this.fullAddr
}
// 获取当前分组的协议
func (this *ServerGroup) Protocol() Protocol {
for _, p := range AllProtocols() {
if strings.HasPrefix(this.fullAddr, p+":") {
return p
}
}
return ProtocolHTTP
}
// 获取当前分组的地址
func (this *ServerGroup) Addr() string {
protocol := this.Protocol()
if protocol == ProtocolUnix {
return strings.TrimPrefix(this.fullAddr, protocol+":")
}
return strings.TrimPrefix(this.fullAddr, protocol+"://")
}
// 判断当前分组是否为HTTP
func (this *ServerGroup) IsHTTP() bool {
p := this.Protocol()
return p == ProtocolHTTP || p == ProtocolHTTP4 || p == ProtocolHTTP6
}
// 判断当前分组是否为HTTPS
func (this *ServerGroup) IsHTTPS() bool {
p := this.Protocol()
return p == ProtocolHTTPS || p == ProtocolHTTPS4 || p == ProtocolHTTPS6
}
// 判断当前分组是否为TCP
func (this *ServerGroup) IsTCP() bool {
p := this.Protocol()
return p == ProtocolTCP || p == ProtocolTCP4 || p == ProtocolTCP6
}
// 判断当前分组是否为TLS
func (this *ServerGroup) IsTLS() bool {
p := this.Protocol()
return p == ProtocolTLS || p == ProtocolTLS4 || p == ProtocolTLS6
}
// 判断当前分组是否为Unix
func (this *ServerGroup) IsUnix() bool {
p := this.Protocol()
return p == ProtocolUnix
}
// 判断当前分组是否为UDP
func (this *ServerGroup) IsUDP() bool {
p := this.Protocol()
return p == ProtocolUDP
}
// 获取第一个Server
func (this *ServerGroup) FirstServer() *ServerConfig {
if len(this.Servers) > 0 {
return this.Servers[0]
}
return nil
}

View File

@@ -1,4 +1,4 @@
package configs package serverconfigs
import ( import (
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"

View File

@@ -0,0 +1,23 @@
package serverconfigs
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
type ServerNameType = string
const (
ServerNameTypeFull = "full" // 完整的域名,包含通配符等
ServerNameTypePrefix = "prefix" // 前缀
ServerNameTypeSuffix = "suffix" // 后缀
ServerNameTypeMatch = "match" // 正则匹配
)
// 主机名(域名)配置
type ServerNameConfig struct {
Name string `yaml:"name" json:"name"` // 名称
Type string `yaml:"type" json:"type"` // 类型
}
// 判断主机名是否匹配
func (this *ServerNameConfig) Match(name string) bool {
return configutils.MatchDomains([]string{this.Name}, name)
}

View File

@@ -0,0 +1,21 @@
package shared
import (
"sync"
)
var Locker = new(FileLocker)
// global file modify locker
type FileLocker struct {
locker sync.Mutex
}
// lock
func (this *FileLocker) Lock() {
this.locker.Lock()
}
func (this *FileLocker) Unlock() {
this.locker.Unlock()
}

View File

@@ -0,0 +1,207 @@
package sslconfigs
import (
"crypto/tls"
"crypto/x509"
"errors"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"net"
"regexp"
"strconv"
"strings"
)
// TLS Version
type TLSVersion = string
// Cipher Suites
type TLSCipherSuite = string
// SSL配置
type SSLConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
Certs []*SSLCertConfig `yaml:"certs" json:"certs"`
ClientAuthType SSLClientAuthType `yaml:"clientAuthType" json:"clientAuthType"` // 客户端认证类型
ClientCACertIds []string `yaml:"clientCACertIds" json:"clientCACertIds"` // 客户端认证CA
Listen []string `yaml:"listen" json:"listen"` // 网络地址
MinVersion TLSVersion `yaml:"minVersion" json:"minVersion"` // 支持的最小版本
CipherSuites []TLSCipherSuite `yaml:"cipherSuites" json:"cipherSuites"` // 加密算法套件
HSTS *HSTSConfig `yaml:"hsts2" json:"hsts"` // HSTS配置yaml之所以使用hsts2是因为要和以前的版本分开
HTTP2Disabled bool `yaml:"http2Disabled" json:"http2Disabled"` // 是否禁用HTTP2
nameMapping map[string]*tls.Certificate // dnsName => cert
minVersion uint16
cipherSuites []uint16
clientCAPool *x509.CertPool
}
// 获取新对象
func NewSSLConfig() *SSLConfig {
return &SSLConfig{}
}
// 校验配置
func (this *SSLConfig) Init() error {
if !this.IsOn {
return nil
}
if len(this.Certs) == 0 {
return errors.New("no certificates in https config")
}
for _, cert := range this.Certs {
err := cert.Init()
if err != nil {
return err
}
}
if this.Listen == nil {
this.Listen = []string{}
} else {
for index, addr := range this.Listen {
_, _, err := net.SplitHostPort(addr)
if err != nil {
this.Listen[index] = strings.TrimSuffix(addr, ":") + ":443"
}
}
}
// min version
this.convertMinVersion()
// cipher suite categories
this.initCipherSuites()
// hsts
if this.HSTS != nil {
err := this.HSTS.Init()
if err != nil {
return err
}
}
// CA证书
if len(this.ClientCACertIds) > 0 && this.ClientAuthType != SSLClientAuthTypeNoClientCert {
this.clientCAPool = x509.NewCertPool()
list := SharedSSLCertList()
for _, certId := range this.ClientCACertIds {
cert := list.FindCert(certId)
if cert == nil {
continue
}
if !cert.On {
continue
}
data, err := ioutil.ReadFile(cert.FullCertPath())
if err != nil {
return err
}
this.clientCAPool.AppendCertsFromPEM(data)
}
}
return nil
}
// 取得最小版本
func (this *SSLConfig) TLSMinVersion() uint16 {
return this.minVersion
}
// 套件
func (this *SSLConfig) TLSCipherSuites() []uint16 {
return this.cipherSuites
}
// 校验是否匹配某个域名
func (this *SSLConfig) MatchDomain(domain string) (cert *tls.Certificate, ok bool) {
for _, cert := range this.Certs {
if cert.MatchDomain(domain) {
return cert.CertObject(), true
}
}
return nil, false
}
// 取得第一个证书
func (this *SSLConfig) FirstCert() *tls.Certificate {
for _, cert := range this.Certs {
return cert.CertObject()
}
return nil
}
// 是否包含某个证书或密钥路径
func (this *SSLConfig) ContainsFile(file string) bool {
for _, cert := range this.Certs {
if cert.CertFile == file || cert.KeyFile == file {
return true
}
}
return false
}
// 删除证书文件
func (this *SSLConfig) DeleteFiles() error {
var resultErr error = nil
for _, cert := range this.Certs {
err := cert.DeleteFiles()
if err != nil {
resultErr = err
}
}
return resultErr
}
// 查找单个证书配置
func (this *SSLConfig) FindCert(certId string) *SSLCertConfig {
for _, cert := range this.Certs {
if cert.Id == certId {
return cert
}
}
return nil
}
// 添加证书
func (this *SSLConfig) AddCert(cert *SSLCertConfig) {
this.Certs = append(this.Certs, cert)
}
// CA证书Pool用于TLS对客户端进行认证
func (this *SSLConfig) CAPool() *x509.CertPool {
return this.clientCAPool
}
// 分解所有监听地址
func (this *SSLConfig) ParseListenAddresses() []string {
result := []string{}
var reg = regexp.MustCompile(`\[\s*(\d+)\s*[,:-]\s*(\d+)\s*]$`)
for _, addr := range this.Listen {
match := reg.FindStringSubmatch(addr)
if len(match) == 0 {
result = append(result, addr)
} else {
min := types.Int(match[1])
max := types.Int(match[2])
if min > max {
min, max = max, min
}
for i := min; i <= max; i++ {
newAddr := reg.ReplaceAllString(addr, ":"+strconv.Itoa(i))
result = append(result, newAddr)
}
}
}
return result
}

View File

@@ -0,0 +1,75 @@
package sslconfigs
import (
"crypto/tls"
"github.com/iwind/TeaGo/maps"
)
// 认证类型
type SSLClientAuthType = int
const (
SSLClientAuthTypeNoClientCert SSLClientAuthType = 0
SSLClientAuthTypeRequestClientCert SSLClientAuthType = 1
SSLClientAuthTypeRequireAnyClientCert SSLClientAuthType = 2
SSLClientAuthTypeVerifyClientCertIfGiven SSLClientAuthType = 3
SSLClientAuthTypeRequireAndVerifyClientCert SSLClientAuthType = 4
)
// 所有的客户端认证类型
func AllSSLClientAuthTypes() []maps.Map {
return []maps.Map{
{
"name": "不需要客户端证书",
"type": SSLClientAuthTypeNoClientCert,
"requireCA": false,
},
{
"name": "请求客户端证书",
"type": SSLClientAuthTypeRequestClientCert,
"requireCA": true,
},
{
"name": "需要客户端证书,但不校验",
"type": SSLClientAuthTypeRequireAnyClientCert,
"requireCA": true,
},
{
"name": "有客户端证书的时候才校验",
"type": SSLClientAuthTypeVerifyClientCertIfGiven,
"requireCA": true,
},
{
"name": "校验客户端提供的证书",
"type": SSLClientAuthTypeRequireAndVerifyClientCert,
"requireCA": true,
},
}
}
// 查找单个认证方式的名称
func FindSSLClientAuthTypeName(authType SSLClientAuthType) string {
for _, m := range AllSSLClientAuthTypes() {
if m.GetInt("type") == authType {
return m.GetString("name")
}
}
return ""
}
// 认证类型和tls包内类型的映射
func GoSSLClientAuthType(authType SSLClientAuthType) tls.ClientAuthType {
switch authType {
case SSLClientAuthTypeNoClientCert:
return tls.NoClientCert
case SSLClientAuthTypeRequestClientCert:
return tls.RequestClientCert
case SSLClientAuthTypeRequireAnyClientCert:
return tls.RequireAnyClientCert
case SSLClientAuthTypeVerifyClientCertIfGiven:
return tls.VerifyClientCertIfGiven
case SSLClientAuthTypeRequireAndVerifyClientCert:
return tls.RequireAndVerifyClientCert
}
return tls.NoClientCert
}

View File

@@ -0,0 +1,271 @@
package sslconfigs
import (
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/utils/string"
"io/ioutil"
"strings"
"time"
)
// SSL证书
type SSLCertConfig struct {
Id string `yaml:"id" json:"id"`
On bool `yaml:"on" json:"on"`
Description string `yaml:"description" json:"description"` // 说明
CertFile string `yaml:"certFile" json:"certFile"`
KeyFile string `yaml:"keyFile" json:"keyFile"`
IsLocal bool `yaml:"isLocal" json:"isLocal"` // 是否为本地文件
TaskId string `yaml:"taskId" json:"taskId"` // 生成证书任务ID
IsShared bool `yaml:"isShared" json:"isShared"` // 是否为公用组件
ServerName string `yaml:"serverName" json:"serverName"` // 证书使用的主机名在请求TLS服务器时需要
IsCA bool `yaml:"isCA" json:"isCA"` // 是否为CA证书
dnsNames []string
cert *tls.Certificate
timeBefore time.Time
timeAfter time.Time
issuer pkix.Name
}
// 获取新的SSL证书
func NewSSLCertConfig(certFile string, keyFile string) *SSLCertConfig {
return &SSLCertConfig{
On: true,
Id: stringutil.Rand(16),
CertFile: certFile,
KeyFile: keyFile,
}
}
// 校验
func (this *SSLCertConfig) Init() error {
if this.IsShared {
shared := this.FindShared()
if shared == nil {
return errors.New("the shared cert has been deleted")
}
// 拷贝之前需要保留的
serverName := this.ServerName
// copy
configutils.CopyStructObject(this, shared)
this.ServerName = serverName
}
this.dnsNames = []string{}
if len(this.CertFile) == 0 {
return errors.New("cert file should not be empty")
}
// 分析证书
if this.IsCA { // CA证书
data, err := ioutil.ReadFile(this.FullCertPath())
if err != nil {
return err
}
index := -1
this.cert = &tls.Certificate{
Certificate: [][]byte{},
}
for {
index++
block, rest := pem.Decode(data)
if block == nil {
break
}
if len(rest) == 0 {
break
}
this.cert.Certificate = append(this.cert.Certificate, block.Bytes)
data = rest
c, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return err
}
if c == nil {
return errors.New("no available certificates in file")
}
dnsNames := c.DNSNames
if len(dnsNames) > 0 {
for _, dnsName := range dnsNames {
if !lists.ContainsString(this.dnsNames, dnsName) {
this.dnsNames = append(this.dnsNames, dnsName)
}
}
}
if index == 0 {
this.timeBefore = c.NotBefore
this.timeAfter = c.NotAfter
this.issuer = c.Issuer
}
}
} else { // 证书+私钥
if len(this.KeyFile) == 0 {
return errors.New("key file should not be empty")
}
cert, err := tls.LoadX509KeyPair(this.FullCertPath(), this.FullKeyPath())
if err != nil {
return errors.New("load certificate '" + this.CertFile + "', '" + this.KeyFile + "' failed:" + err.Error())
}
for index, data := range cert.Certificate {
c, err := x509.ParseCertificate(data)
if err != nil {
continue
}
dnsNames := c.DNSNames
if len(dnsNames) > 0 {
for _, dnsName := range dnsNames {
if !lists.ContainsString(this.dnsNames, dnsName) {
this.dnsNames = append(this.dnsNames, dnsName)
}
}
}
if index == 0 {
this.timeBefore = c.NotBefore
this.timeAfter = c.NotAfter
this.issuer = c.Issuer
}
}
this.cert = &cert
}
return nil
}
// 查找共享的证书
func (this *SSLCertConfig) FindShared() *SSLCertConfig {
if !this.IsShared {
return nil
}
return SharedSSLCertList().FindCert(this.Id)
}
// 证书文件路径
func (this *SSLCertConfig) FullCertPath() string {
if len(this.CertFile) == 0 {
return ""
}
if !strings.ContainsAny(this.CertFile, "/\\") {
return Tea.ConfigFile(this.CertFile)
}
return this.CertFile
}
// 密钥文件路径
func (this *SSLCertConfig) FullKeyPath() string {
if len(this.KeyFile) == 0 {
return ""
}
if !strings.ContainsAny(this.KeyFile, "/\\") {
return Tea.ConfigFile(this.KeyFile)
}
return this.KeyFile
}
// 校验是否匹配某个域名
func (this *SSLCertConfig) MatchDomain(domain string) bool {
if len(this.dnsNames) == 0 {
return false
}
return configutils.MatchDomains(this.dnsNames, domain)
}
// 证书中的域名
func (this *SSLCertConfig) DNSNames() []string {
return this.dnsNames
}
// 获取证书对象
func (this *SSLCertConfig) CertObject() *tls.Certificate {
return this.cert
}
// 开始时间
func (this *SSLCertConfig) TimeBefore() time.Time {
return this.timeBefore
}
// 结束时间
func (this *SSLCertConfig) TimeAfter() time.Time {
return this.timeAfter
}
// 发行信息
func (this *SSLCertConfig) Issuer() pkix.Name {
return this.issuer
}
// 删除文件
func (this *SSLCertConfig) DeleteFiles() error {
if this.IsLocal {
return nil
}
var resultErr error = nil
if len(this.CertFile) > 0 && !strings.ContainsAny(this.CertFile, "/\\") {
err := files.NewFile(this.FullCertPath()).Delete()
if err != nil {
resultErr = err
}
}
if len(this.KeyFile) > 0 && !strings.ContainsAny(this.KeyFile, "/\\") {
err := files.NewFile(this.FullKeyPath()).Delete()
if err != nil {
resultErr = err
}
}
return resultErr
}
// 读取证书文件
func (this *SSLCertConfig) ReadCert() ([]byte, error) {
if len(this.CertFile) == 0 {
return nil, errors.New("cert file should not be empty")
}
if this.IsLocal {
return ioutil.ReadFile(this.CertFile)
}
return ioutil.ReadFile(Tea.ConfigFile(this.CertFile))
}
// 读取密钥文件
func (this *SSLCertConfig) ReadKey() ([]byte, error) {
if len(this.KeyFile) == 0 {
return nil, errors.New("key file should not be empty")
}
if this.IsLocal {
return ioutil.ReadFile(this.KeyFile)
}
return ioutil.ReadFile(Tea.ConfigFile(this.KeyFile))
}
// 匹配关键词
func (this *SSLCertConfig) MatchKeyword(keyword string) (matched bool, name string, tags []string) {
if configutils.MatchKeyword(this.Description, keyword) {
matched = true
name = this.Description
}
return
}

View File

@@ -0,0 +1,86 @@
package sslconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/shared"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/logs"
"gopkg.in/yaml.v3"
"io/ioutil"
)
const (
sslCertListFilename = "ssl.certs.conf"
)
// 获取证书列表实例
// 一定会返回不为nil的值
func SharedSSLCertList() *SSLCertList {
data, err := ioutil.ReadFile(Tea.ConfigFile(sslCertListFilename))
if err != nil {
return NewSSLCertList()
}
list := &SSLCertList{}
err = yaml.Unmarshal(data, list)
if err != nil {
logs.Error(err)
return NewSSLCertList()
}
return list
}
// 公共的SSL证书列表
type SSLCertList struct {
Certs []*SSLCertConfig `yaml:"certs" json:"certs"` // 证书
}
// 获取新对象
func NewSSLCertList() *SSLCertList {
return &SSLCertList{
Certs: []*SSLCertConfig{},
}
}
// 添加证书
func (this *SSLCertList) AddCert(cert *SSLCertConfig) {
this.Certs = append(this.Certs, cert)
}
// 删除证书
func (this *SSLCertList) RemoveCert(certId string) {
result := []*SSLCertConfig{}
for _, cert := range this.Certs {
if cert.Id == certId {
continue
}
result = append(result, cert)
}
this.Certs = result
}
// 查找证书
func (this *SSLCertList) FindCert(certId string) *SSLCertConfig {
if len(certId) == 0 {
return nil
}
for _, cert := range this.Certs {
if cert.Id == certId {
return cert
}
}
return nil
}
// 保存
func (this *SSLCertList) Save() error {
shared.Locker.Lock()
defer shared.Locker.Unlock()
data, err := yaml.Marshal(this)
if err != nil {
return err
}
return ioutil.WriteFile(Tea.ConfigFile(sslCertListFilename), data, 0777)
}

View File

@@ -0,0 +1,124 @@
// +build !go1.12
package sslconfigs
import "crypto/tls"
var AllTlsVersions = []TLSVersion{"SSL 3.0", "TLS 1.0", "TLS 1.1", "TLS 1.2"}
var AllTLSCipherSuites = []TLSCipherSuite{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
}
var TLSModernCipherSuites = []string{
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
var TLSIntermediateCipherSuites = []string{
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
}
func (this *SSLConfig) convertMinVersion() {
switch this.MinVersion {
case "SSL 3.0":
this.minVersion = tls.VersionSSL30
case "TLS 1.0":
this.minVersion = tls.VersionTLS10
case "TLS 1.1":
this.minVersion = tls.VersionTLS11
case "TLS 1.2":
this.minVersion = tls.VersionTLS12
default:
this.minVersion = tls.VersionTLS10
}
}
func (this *SSLConfig) initCipherSuites() {
// cipher suites
suites := []uint16{}
for _, suite := range this.CipherSuites {
switch suite {
case "TLS_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_RSA_WITH_RC4_128_SHA)
case "TLS_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case "TLS_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
}
}
this.cipherSuites = suites
}

View File

@@ -0,0 +1,148 @@
// +build go1.12
package sslconfigs
import (
"crypto/tls"
"os"
)
var AllTlsVersions = []TLSVersion{"SSL 3.0", "TLS 1.0", "TLS 1.1", "TLS 1.2", "TLS 1.3"}
var AllTLSCipherSuites = []TLSCipherSuite{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
}
var TLSModernCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
}
var TLSIntermediateCipherSuites = []string{
"TLS_AES_128_GCM_SHA256",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
}
func (this *SSLConfig) convertMinVersion() {
switch this.MinVersion {
case "SSL 3.0":
this.minVersion = tls.VersionSSL30
case "TLS 1.0":
this.minVersion = tls.VersionTLS10
case "TLS 1.1":
this.minVersion = tls.VersionTLS11
case "TLS 1.2":
this.minVersion = tls.VersionTLS12
case "TLS 1.3":
this.minVersion = tls.VersionTLS13
os.Setenv("GODEBUG", "tls13=1") // TODO should be removed in go 1.14, in go 1.12 tls IS NOT FULL IMPLEMENTED YET
default:
this.minVersion = tls.VersionTLS10
}
}
func (this *SSLConfig) initCipherSuites() {
// cipher suites
suites := []uint16{}
for _, suite := range this.CipherSuites {
switch suite {
case "TLS_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_RSA_WITH_RC4_128_SHA)
case "TLS_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case "TLS_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case "TLS_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_RC4_128_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305)
case "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305":
suites = append(suites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305)
case "TLS_AES_128_GCM_SHA256":
suites = append(suites, tls.TLS_AES_128_GCM_SHA256)
case "TLS_AES_256_GCM_SHA384":
suites = append(suites, tls.TLS_AES_256_GCM_SHA384)
case "TLS_CHACHA20_POLY1305_SHA256":
suites = append(suites, tls.TLS_CHACHA20_POLY1305_SHA256)
}
}
this.cipherSuites = suites
}

View File

@@ -0,0 +1,63 @@
package sslconfigs
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/configutils"
"strconv"
"strings"
)
// HSTS设置
// 参考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
type HSTSConfig struct {
On bool `yaml:"on" json:"on"`
MaxAge int `yaml:"maxAge" json:"maxAge"` // 单位秒
IncludeSubDomains bool `yaml:"includeSubDomains" json:"includeSubDomains"`
Preload bool `yaml:"preload" json:"preload"`
Domains []string `yaml:"domains" json:"domains"`
hasDomains bool
headerValue string
}
// 校验
func (this *HSTSConfig) Init() error {
this.hasDomains = len(this.Domains) > 0
this.headerValue = this.asHeaderValue()
return nil
}
// 判断是否匹配域名
func (this *HSTSConfig) Match(domain string) bool {
if !this.hasDomains {
return true
}
return configutils.MatchDomains(this.Domains, domain)
}
// Header Key
func (this *HSTSConfig) HeaderKey() string {
return "Strict-Transport-Security"
}
// 取得当前的Header值
func (this *HSTSConfig) HeaderValue() string {
return this.headerValue
}
// 转换为Header值
func (this *HSTSConfig) asHeaderValue() string {
b := strings.Builder{}
b.WriteString("max-age=")
if this.MaxAge > 0 {
b.WriteString(strconv.Itoa(this.MaxAge))
} else {
b.WriteString("31536000") // 1 year
}
if this.IncludeSubDomains {
b.WriteString("; includeSubDomains")
}
if this.Preload {
b.WriteString("; preload")
}
return b.String()
}

View File

@@ -0,0 +1,39 @@
package sslconfigs
import (
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestHSTSConfig(t *testing.T) {
h := &HSTSConfig{}
h.Init()
t.Log(h.HeaderValue())
h.IncludeSubDomains = true
h.Init()
t.Log(h.HeaderValue())
h.Preload = true
h.Init()
t.Log(h.HeaderValue())
h.IncludeSubDomains = false
h.Init()
t.Log(h.HeaderValue())
h.MaxAge = 86400
h.Init()
t.Log(h.HeaderValue())
a := assert.NewAssertion(t)
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"abc.com"}
h.Init()
a.IsTrue(h.Match("abc.com"))
h.Domains = []string{"1.abc.com"}
h.Init()
a.IsFalse(h.Match("abc.com"))
}

View File

@@ -0,0 +1,10 @@
package serverconfigs
type WebConfig struct {
IsOn bool `yaml:"isOn" json:"isOn"`
Locations []*LocationConfig `yaml:"locations" json:"locations"` // 路径规则 TODO
// 本地静态资源配置
Root string `yaml:"root" json:"root"` // 资源根目录 TODO
}

View File

@@ -1,8 +0,0 @@
package configs
type WebConfig struct {
Locations []*LocationConfig `yaml:"locations"` // 路径规则
// 本地静态资源配置
Root string `yaml:"root" json:"root"` // 资源根目录
}

13
internal/const/const.go Normal file
View File

@@ -0,0 +1,13 @@
package teaconst
const (
Version = "0.0.1"
ProductName = "Edge Node"
ProcessName = "edge-node"
Role = "node"
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
EncryptMethod = "aes-256-cfb"
)

View File

@@ -0,0 +1,41 @@
package encrypt
import (
"github.com/iwind/TeaGo/logs"
)
const (
MagicKey = "f1c8eafb543f03023e97b7be864a4e9b"
)
// 加密特殊信息
func MagicKeyEncode(data []byte) []byte {
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
dst, err := method.Encrypt(data)
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
return dst
}
// 解密特殊信息
func MagicKeyDecode(data []byte) []byte {
method, err := NewMethodInstance("aes-256-cfb", MagicKey, MagicKey[:16])
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
src, err := method.Decrypt(data)
if err != nil {
logs.Println("[MagicKeyEncode]" + err.Error())
return data
}
return src
}

View File

@@ -0,0 +1,11 @@
package encrypt
import "testing"
func TestMagicKeyEncode(t *testing.T) {
dst := MagicKeyEncode([]byte("Hello,World"))
t.Log("dst:", string(dst))
src := MagicKeyDecode(dst)
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,12 @@
package encrypt
type MethodInterface interface {
// 初始化
Init(key []byte, iv []byte) error
// 加密
Encrypt(src []byte) (dst []byte, err error)
// 解密
Decrypt(dst []byte) (src []byte, err error)
}

View File

@@ -0,0 +1,73 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES128CFBMethod struct {
iv []byte
block cipher.Block
}
func (this *AES128CFBMethod) Init(key, iv []byte) error {
// 判断key是否为32长度
l := len(key)
if l > 16 {
key = key[:16]
} else if l < 16 {
key = append(key, bytes.Repeat([]byte{' '}, 16-l)...)
}
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
// block
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
return nil
}
func (this *AES128CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES128CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
encrypter := cipher.NewCFBDecrypter(this.block, this.iv)
encrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,92 @@
package encrypt
import (
"runtime"
"strings"
"testing"
)
func TestAES128CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src = make([]byte, len(src))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func TestAES128CFBMethod_Encrypt2(t *testing.T) {
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
sources := [][]byte{}
{
a := []byte{1}
_, err = method.Encrypt(a)
if err != nil {
t.Fatal(err)
}
}
for i := 0; i < 10; i++ {
src := []byte(strings.Repeat("Hello", 1))
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
sources = append(sources, dst)
}
{
a := []byte{1}
_, err = method.Decrypt(a)
if err != nil {
t.Fatal(err)
}
}
for _, dst := range sources {
dst2 := append([]byte{}, dst...)
src2 := make([]byte, len(dst2))
src2, err := method.Decrypt(dst2)
if err != nil {
t.Fatal(err)
}
t.Log(string(src2))
}
}
func BenchmarkAES128CFBMethod_Encrypt(b *testing.B) {
runtime.GOMAXPROCS(1)
method, err := NewMethodInstance("aes-128-cfb", "abc", "123")
if err != nil {
b.Fatal(err)
}
src := []byte(strings.Repeat("Hello", 1024))
for i := 0; i < b.N; i++ {
dst, err := method.Encrypt(src)
if err != nil {
b.Fatal(err)
}
_ = dst
}
}

View File

@@ -0,0 +1,74 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES192CFBMethod struct {
block cipher.Block
iv []byte
}
func (this *AES192CFBMethod) Init(key, iv []byte) error {
// 判断key是否为24长度
l := len(key)
if l > 24 {
key = key[:24]
} else if l < 24 {
key = append(key, bytes.Repeat([]byte{' '}, 24-l)...)
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
return nil
}
func (this *AES192CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES192CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
decrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,45 @@
package encrypt
import (
"runtime"
"strings"
"testing"
)
func TestAES192CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func BenchmarkAES192CFBMethod_Encrypt(b *testing.B) {
runtime.GOMAXPROCS(1)
method, err := NewMethodInstance("aes-192-cfb", "abc", "123")
if err != nil {
b.Fatal(err)
}
src := []byte(strings.Repeat("Hello", 1024))
for i := 0; i < b.N; i++ {
dst, err := method.Encrypt(src)
if err != nil {
b.Fatal(err)
}
_ = dst
}
}

View File

@@ -0,0 +1,72 @@
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
)
type AES256CFBMethod struct {
block cipher.Block
iv []byte
}
func (this *AES256CFBMethod) Init(key, iv []byte) error {
// 判断key是否为32长度
l := len(key)
if l > 32 {
key = key[:32]
} else if l < 32 {
key = append(key, bytes.Repeat([]byte{' '}, 32-l)...)
}
block, err := aes.NewCipher(key)
if err != nil {
return err
}
this.block = block
// 判断iv长度
l2 := len(iv)
if l2 > aes.BlockSize {
iv = iv[:aes.BlockSize]
} else if l2 < aes.BlockSize {
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-l2)...)
}
this.iv = iv
return nil
}
func (this *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
dst = make([]byte, len(src))
encrypter := cipher.NewCFBEncrypter(this.block, this.iv)
encrypter.XORKeyStream(dst, src)
return
}
func (this *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
defer func() {
err = RecoverMethodPanic(recover())
}()
src = make([]byte, len(dst))
decrypter := cipher.NewCFBDecrypter(this.block, this.iv)
decrypter.XORKeyStream(src, dst)
return
}

View File

@@ -0,0 +1,42 @@
package encrypt
import "testing"
func TestAES256CFBMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}
func TestAES256CFBMethod_Encrypt2(t *testing.T) {
method, err := NewMethodInstance("aes-256-cfb", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,26 @@
package encrypt
type RawMethod struct {
}
func (this *RawMethod) Init(key, iv []byte) error {
return nil
}
func (this *RawMethod) Encrypt(src []byte) (dst []byte, err error) {
if len(src) == 0 {
return
}
dst = make([]byte, len(src))
copy(dst, src)
return
}
func (this *RawMethod) Decrypt(dst []byte) (src []byte, err error) {
if len(dst) == 0 {
return
}
src = make([]byte, len(dst))
copy(src, dst)
return
}

View File

@@ -0,0 +1,23 @@
package encrypt
import "testing"
func TestRawMethod_Encrypt(t *testing.T) {
method, err := NewMethodInstance("raw", "abc", "123")
if err != nil {
t.Fatal(err)
}
src := []byte("Hello, World")
dst, err := method.Encrypt(src)
if err != nil {
t.Fatal(err)
}
dst = dst[:len(src)]
t.Log("dst:", string(dst))
src, err = method.Decrypt(dst)
if err != nil {
t.Fatal(err)
}
t.Log("src:", string(src))
}

View File

@@ -0,0 +1,43 @@
package encrypt
import (
"errors"
"reflect"
)
var methods = map[string]reflect.Type{
"raw": reflect.TypeOf(new(RawMethod)).Elem(),
"aes-128-cfb": reflect.TypeOf(new(AES128CFBMethod)).Elem(),
"aes-192-cfb": reflect.TypeOf(new(AES192CFBMethod)).Elem(),
"aes-256-cfb": reflect.TypeOf(new(AES256CFBMethod)).Elem(),
}
func NewMethodInstance(method string, key string, iv string) (MethodInterface, error) {
valueType, ok := methods[method]
if !ok {
return nil, errors.New("method '" + method + "' not found")
}
instance, ok := reflect.New(valueType).Interface().(MethodInterface)
if !ok {
return nil, errors.New("method '" + method + "' must implement MethodInterface")
}
err := instance.Init([]byte(key), []byte(iv))
return instance, err
}
func RecoverMethodPanic(err interface{}) error {
if err != nil {
s, ok := err.(string)
if ok {
return errors.New(s)
}
e, ok := err.(error)
if ok {
return e
}
return errors.New("unknown error")
}
return nil
}

View File

@@ -0,0 +1,8 @@
package encrypt
import "testing"
func TestFindMethodInstance(t *testing.T) {
t.Log(NewMethodInstance("a", "b", ""))
t.Log(NewMethodInstance("aes-256-cfb", "123456", ""))
}

View File

@@ -3,17 +3,16 @@ package nodes
import ( import (
"context" "context"
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"net" "net"
"net/http"
"sync" "sync"
) )
type Listener struct { type Listener struct {
group *configs.ServerGroup group *serverconfigs.ServerGroup
isListening bool isListening bool
listener interface{} // 监听器 listener ListenerImpl // 监听器
locker sync.RWMutex locker sync.RWMutex
} }
@@ -22,7 +21,7 @@ func NewListener() *Listener {
return &Listener{} return &Listener{}
} }
func (this *Listener) Reload(group *configs.ServerGroup) { func (this *Listener) Reload(group *serverconfigs.ServerGroup) {
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
this.group = group this.group = group
@@ -40,78 +39,67 @@ func (this *Listener) Listen() error {
return nil return nil
} }
protocol := this.group.Protocol() protocol := this.group.Protocol()
switch protocol {
case configs.ProtocolHTTP, configs.ProtocolHTTP4, configs.ProtocolHTTP6:
return this.listenHTTP()
case configs.ProtocolHTTPS, configs.ProtocolHTTPS4, configs.ProtocolHTTPS6:
return this.ListenHTTPS()
case configs.ProtocolTCP, configs.ProtocolTCP4, configs.ProtocolTCP6:
return this.listenTCP()
case configs.ProtocolTLS, configs.ProtocolTLS4, configs.ProtocolTLS6:
return this.listenTLS()
case configs.ProtocolUnix:
return this.listenUnix()
case configs.ProtocolUDP:
return this.listenUDP()
default:
return errors.New("unknown protocol '" + protocol + "'")
}
}
func (this *Listener) Close() error { netListener, err := this.createListener()
// TODO 需要实现
return nil
}
func (this *Listener) listenHTTP() error {
listener, err := this.createListener()
if err != nil { if err != nil {
return err return err
} }
mux := http.NewServeMux() switch protocol {
mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { case serverconfigs.ProtocolHTTP, serverconfigs.ProtocolHTTP4, serverconfigs.ProtocolHTTP6:
_, _ = writer.Write([]byte("Hello, World")) this.listener = &HTTPListener{
}) Group: this.group,
server := &http.Server{ Listener: netListener,
Addr: this.group.Addr(), }
Handler: mux, case serverconfigs.ProtocolHTTPS, serverconfigs.ProtocolHTTPS4, serverconfigs.ProtocolHTTPS6:
this.listener = &HTTPListener{
Group: this.group,
Listener: netListener,
}
case serverconfigs.ProtocolTCP, serverconfigs.ProtocolTCP4, serverconfigs.ProtocolTCP6:
this.listener = &TCPListener{
Group: this.group,
Listener: netListener,
}
case serverconfigs.ProtocolTLS, serverconfigs.ProtocolTLS4, serverconfigs.ProtocolTLS6:
this.listener = &TCPListener{
Group: this.group,
Listener: netListener,
}
case serverconfigs.ProtocolUnix:
this.listener = &UnixListener{
Group: this.group,
Listener: netListener,
}
case serverconfigs.ProtocolUDP:
this.listener = &UDPListener{
Group: this.group,
Listener: netListener,
}
default:
return errors.New("unknown protocol '" + protocol + "'")
} }
this.listener.Init()
go func() { go func() {
err = server.Serve(listener) err := this.listener.Serve()
if err != nil { if err != nil {
logs.Println("[LISTENER]" + err.Error()) logs.Println("[LISTENER]" + err.Error())
} }
}() }()
return nil return nil
} }
func (this *Listener) ListenHTTPS() error { func (this *Listener) Close() error {
// TODO 需要实现 if this.listener == nil {
return nil return nil
} }
return this.listener.Close()
func (this *Listener) listenTCP() error {
// TODO 需要实现
return nil
}
func (this *Listener) listenTLS() error {
// TODO 需要实现
return nil
}
func (this *Listener) listenUnix() error {
// TODO 需要实现
return nil
}
func (this *Listener) listenUDP() error {
// TODO 需要实现
return nil
} }
// 创建监听器
func (this *Listener) createListener() (net.Listener, error) { func (this *Listener) createListener() (net.Listener, error) {
listenConfig := net.ListenConfig{ listenConfig := net.ListenConfig{
Control: nil, Control: nil,
@@ -119,9 +107,9 @@ func (this *Listener) createListener() (net.Listener, error) {
} }
switch this.group.Protocol() { switch this.group.Protocol() {
case configs.ProtocolHTTP4, configs.ProtocolHTTPS4, configs.ProtocolTLS4: case serverconfigs.ProtocolHTTP4, serverconfigs.ProtocolHTTPS4, serverconfigs.ProtocolTLS4:
return listenConfig.Listen(context.Background(), "tcp4", this.group.Addr()) return listenConfig.Listen(context.Background(), "tcp4", this.group.Addr())
case configs.ProtocolHTTP6, configs.ProtocolHTTPS6, configs.ProtocolTLS6: case serverconfigs.ProtocolHTTP6, serverconfigs.ProtocolHTTPS6, serverconfigs.ProtocolTLS6:
return listenConfig.Listen(context.Background(), "tcp6", this.group.Addr()) return listenConfig.Listen(context.Background(), "tcp6", this.group.Addr())
} }

View File

@@ -0,0 +1,195 @@
package nodes
import (
"crypto/tls"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs/sslconfigs"
http2 "golang.org/x/net/http2"
"sync"
)
type BaseListener struct {
serversLocker sync.RWMutex
namedServersLocker sync.RWMutex
namedServers map[string]*NamedServer // 域名 => server
}
// 初始化
func (this *BaseListener) Init() {
this.namedServers = map[string]*NamedServer{}
}
// 构造TLS配置
func (this *BaseListener) buildTLSConfig(group *serverconfigs.ServerGroup) *tls.Config {
return &tls.Config{
Certificates: nil,
GetConfigForClient: func(info *tls.ClientHelloInfo) (config *tls.Config, e error) {
ssl, _, err := this.matchSSL(group, info.ServerName)
if err != nil {
return nil, err
}
cipherSuites := ssl.TLSCipherSuites()
if len(cipherSuites) == 0 {
cipherSuites = nil
}
nextProto := []string{}
if !ssl.HTTP2Disabled {
nextProto = []string{http2.NextProtoTLS}
}
return &tls.Config{
Certificates: nil,
MinVersion: ssl.TLSMinVersion(),
CipherSuites: cipherSuites,
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
_, cert, err := this.matchSSL(group, info.ServerName)
if err != nil {
return nil, err
}
if cert == nil {
return nil, errors.New("[proxy]no certs found for '" + info.ServerName + "'")
}
return cert, nil
},
ClientAuth: sslconfigs.GoSSLClientAuthType(ssl.ClientAuthType),
ClientCAs: ssl.CAPool(),
NextProtos: nextProto,
}, nil
},
GetCertificate: func(info *tls.ClientHelloInfo) (certificate *tls.Certificate, e error) {
_, cert, err := this.matchSSL(group, info.ServerName)
if err != nil {
return nil, err
}
if cert == nil {
return nil, errors.New("[proxy]no certs found for '" + info.ServerName + "'")
}
return cert, nil
},
}
}
// 根据域名匹配证书
func (this *BaseListener) matchSSL(group *serverconfigs.ServerGroup, domain string) (*sslconfigs.SSLConfig, *tls.Certificate, error) {
this.serversLocker.RLock()
defer this.serversLocker.RUnlock()
// 如果域名为空,则取第一个
// 通常域名为空是因为是直接通过IP访问的
if len(domain) == 0 {
if serverconfigs.SharedGlobalConfig().HTTPAll.MatchDomainStrictly {
return nil, nil, errors.New("no tls server name matched")
}
firstServer := group.FirstServer()
if firstServer == nil {
return nil, nil, errors.New("no server available")
}
sslConfig := firstServer.SSLConfig()
if sslConfig != nil {
return sslConfig, sslConfig.FirstCert(), nil
}
return nil, nil, errors.New("no tls server name found")
}
// 通过代理服务域名配置匹配
server, _ := this.findNamedServer(group, domain)
if server == nil || server.SSLConfig() == nil || !server.SSLConfig().IsOn {
// 搜索所有的Server通过SSL证书内容中的DNSName匹配
for _, server := range group.Servers {
if server.SSLConfig() == nil || !server.SSLConfig().IsOn {
continue
}
cert, ok := server.SSLConfig().MatchDomain(domain)
if ok {
return server.SSLConfig(), cert, nil
}
}
return nil, nil, errors.New("[proxy]no server found for '" + domain + "'")
}
// 证书是否匹配
sslConfig := server.SSLConfig()
cert, ok := sslConfig.MatchDomain(domain)
if ok {
return sslConfig, cert, nil
}
return sslConfig, sslConfig.FirstCert(), nil
}
// 根据域名来查找匹配的域名
func (this *BaseListener) findNamedServer(group *serverconfigs.ServerGroup, name string) (serverConfig *serverconfigs.ServerConfig, serverName string) {
// 读取缓存
this.namedServersLocker.RLock()
namedServer, found := this.namedServers[name]
if found {
this.namedServersLocker.RUnlock()
return namedServer.Server, namedServer.Name
}
this.namedServersLocker.RUnlock()
this.serversLocker.RLock()
defer this.serversLocker.RUnlock()
currentServers := group.Servers
countServers := len(currentServers)
if countServers == 0 {
return nil, ""
}
// 只记录N个记录防止内存耗尽
maxNamedServers := 10240
// 是否严格匹配域名
matchDomainStrictly := serverconfigs.SharedGlobalConfig().HTTPAll.MatchDomainStrictly
// 如果只有一个server则默认为这个
if countServers == 1 && !matchDomainStrictly {
return currentServers[0], name
}
// 精确查找
for _, server := range currentServers {
if server.MatchNameStrictly(name) {
this.namedServersLocker.Lock()
if len(this.namedServers) < maxNamedServers {
this.namedServers[name] = &NamedServer{
Name: name,
Server: server,
}
}
this.namedServersLocker.Unlock()
return server, name
}
}
// 模糊查找
for _, server := range currentServers {
if matched := server.MatchName(name); matched {
this.namedServersLocker.Lock()
if len(this.namedServers) < maxNamedServers {
this.namedServers[name] = &NamedServer{
Name: name,
Server: server,
}
}
this.namedServersLocker.Unlock()
return server, name
}
}
// 找不到而且域名严格匹配模式下不返回Server
if matchDomainStrictly {
return nil, name
}
// 如果没有找到,则匹配到第一个
return currentServers[0], name
}

View File

@@ -0,0 +1,68 @@
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"github.com/iwind/TeaGo/logs"
"golang.org/x/net/http2"
"net"
"net/http"
"time"
)
type HTTPListener struct {
BaseListener
Group *serverconfigs.ServerGroup
Listener net.Listener
httpServer *http.Server
}
func (this *HTTPListener) Serve() error {
handler := http.NewServeMux()
handler.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
this.handleHTTP(writer, request)
})
this.httpServer = &http.Server{
Addr: this.Group.Addr(),
Handler: handler,
IdleTimeout: 2 * time.Minute,
}
this.httpServer.SetKeepAlivesEnabled(true)
// HTTP协议
if this.Group.IsHTTP() {
err := this.httpServer.Serve(this.Listener)
if err != nil && err != http.ErrServerClosed {
return err
}
}
// HTTPS协议
if this.Group.IsHTTPS() {
this.httpServer.TLSConfig = this.buildTLSConfig(this.Group)
// support http/2
err := http2.ConfigureServer(this.httpServer, nil)
if err != nil {
logs.Println("[HTTP_LISTENER]configure http2 error: " + err.Error())
}
err = this.httpServer.ServeTLS(this.Listener, "", "")
if err != nil && err != http.ErrServerClosed {
return err
}
}
return nil
}
func (this *HTTPListener) Close() error {
// TODO
return nil
}
func (this *HTTPListener) handleHTTP(writer http.ResponseWriter, req *http.Request) {
writer.Write([]byte("Hello, World"))
}

View File

@@ -0,0 +1,13 @@
package nodes
// 各协议监听器的具体实现
type ListenerImpl interface {
// 初始化
Init()
// 监听
Serve() error
// 关闭
Close() error
}

View File

@@ -4,6 +4,8 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"net/url"
"regexp"
"sync" "sync"
) )
@@ -12,6 +14,7 @@ var sharedListenerManager = NewListenerManager()
type ListenerManager struct { type ListenerManager struct {
listenersMap map[string]*Listener // addr => *Listener listenersMap map[string]*Listener // addr => *Listener
locker sync.Mutex locker sync.Mutex
lastConfig *configs.NodeConfig
} }
func NewListenerManager() *ListenerManager { func NewListenerManager() *ListenerManager {
@@ -24,6 +27,18 @@ func (this *ListenerManager) Start(node *configs.NodeConfig) error {
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
// 检查是否有变化
if this.lastConfig != nil && this.lastConfig.Version == node.Version {
return nil
}
this.lastConfig = node
// 初始化
err := node.Init()
if err != nil {
return err
}
// 所有的新地址 // 所有的新地址
groupAddrs := []string{} groupAddrs := []string{}
for _, group := range node.AvailableGroups() { for _, group := range node.AvailableGroups() {
@@ -45,15 +60,16 @@ func (this *ListenerManager) Start(node *configs.NodeConfig) error {
addr := group.FullAddr() addr := group.FullAddr()
listener, ok := this.listenersMap[addr] listener, ok := this.listenersMap[addr]
if ok { if ok {
logs.Println("[LISTENER_MANAGER]reload '" + addr + "'") logs.Println("[LISTENER_MANAGER]reload '" + this.prettyAddress(addr) + "'")
listener.Reload(group) listener.Reload(group)
} else { } else {
logs.Println("[LISTENER_MANAGER]listen '" + addr + "'") logs.Println("[LISTENER_MANAGER]listen '" + this.prettyAddress(addr) + "'")
listener = NewListener() listener = NewListener()
listener.Reload(group) listener.Reload(group)
err := listener.Listen() err := listener.Listen()
if err != nil { if err != nil {
return err logs.Println("[LISTENER_MANAGER]" + err.Error())
continue
} }
this.listenersMap[addr] = listener this.listenersMap[addr] = listener
} }
@@ -61,3 +77,14 @@ func (this *ListenerManager) Start(node *configs.NodeConfig) error {
return nil return nil
} }
func (this *ListenerManager) prettyAddress(addr string) string {
u, err := url.Parse(addr)
if err != nil {
return addr
}
if regexp.MustCompile(`^:\d+$`).MatchString(u.Host) {
u.Host = "*" + u.Host
}
return u.String()
}

View File

@@ -12,15 +12,29 @@ func TestListenerManager_Listen(t *testing.T) {
{ {
IsOn: true, IsOn: true,
HTTP: &configs.HTTPProtocolConfig{ HTTP: &configs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: configs.BaseProtocol{
Listen: []string{"127.0.0.1:1234"}, IsOn: true,
Listen: []*configs.NetworkAddressConfig{
{
Protocol: configs.ProtocolHTTP,
PortRange: "1234",
},
},
},
}, },
}, },
{ {
IsOn: true, IsOn: true,
HTTP: &configs.HTTPProtocolConfig{ HTTP: &configs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: configs.BaseProtocol{
Listen: []string{"127.0.0.1:1235"}, IsOn: true,
Listen: []*configs.NetworkAddressConfig{
{
Protocol: configs.ProtocolHTTP,
PortRange: "1235",
},
},
},
}, },
}, },
}, },
@@ -34,15 +48,29 @@ func TestListenerManager_Listen(t *testing.T) {
{ {
IsOn: true, IsOn: true,
HTTP: &configs.HTTPProtocolConfig{ HTTP: &configs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: configs.BaseProtocol{
Listen: []string{"127.0.0.1:1234"}, IsOn: true,
Listen: []*configs.NetworkAddressConfig{
{
Protocol: configs.ProtocolHTTP,
PortRange: "1234",
},
},
},
}, },
}, },
{ {
IsOn: true, IsOn: true,
HTTP: &configs.HTTPProtocolConfig{ HTTP: &configs.HTTPProtocolConfig{
IsOn: true, BaseProtocol: configs.BaseProtocol{
Listen: []string{"127.0.0.1:1236"}, IsOn: true,
Listen: []*configs.NetworkAddressConfig{
{
Protocol: configs.ProtocolHTTP,
PortRange: "1236",
},
},
},
}, },
}, },
}, },

View File

@@ -0,0 +1,23 @@
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"net"
)
type TCPListener struct {
BaseListener
Group *serverconfigs.ServerGroup
Listener net.Listener
}
func (this *TCPListener) Serve() error {
// TODO
return nil
}
func (this *TCPListener) Close() error {
// TODO
return nil
}

View File

@@ -0,0 +1,23 @@
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"net"
)
type UDPListener struct {
BaseListener
Group *serverconfigs.ServerGroup
Listener net.Listener
}
func (this *UDPListener) Serve() error {
// TODO
return nil
}
func (this *UDPListener) Close() error {
// TODO
return nil
}

View File

@@ -0,0 +1,23 @@
package nodes
import (
"github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
"net"
)
type UnixListener struct {
BaseListener
Group *serverconfigs.ServerGroup
Listener net.Listener
}
func (this *UnixListener) Serve() error {
// TODO
return nil
}
func (this *UnixListener) Close() error {
// TODO
return nil
}

View File

@@ -0,0 +1,9 @@
package nodes
import "github.com/TeaOSLab/EdgeNode/internal/configs/serverconfigs"
// 域名和服务映射
type NamedServer struct {
Name string // 匹配后的域名
Server *serverconfigs.ServerConfig // 匹配后的服务配置
}

View File

@@ -1,14 +1,20 @@
package nodes package nodes
import ( import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeNode/internal/configs" "github.com/TeaOSLab/EdgeNode/internal/configs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"time"
) )
var sharedNodeConfig *configs.NodeConfig = nil
var stop = make(chan bool) var stop = make(chan bool)
var lastVersion = -1
// 节点
type Node struct { type Node struct {
} }
@@ -17,13 +23,24 @@ func NewNode() *Node {
} }
func (this *Node) Start() { func (this *Node) Start() {
// 读取API配置
err := this.syncConfig(false)
if err != nil {
logs.Println(err.Error())
}
// 启动同步计时器
this.startSyncTimer()
// 状态变更计时器
go NewNodeStatusExecutor().Listen()
// 读取配置 // 读取配置
nodeConfig, err := configs.SharedNodeConfig() nodeConfig, err := configs.SharedNodeConfig()
if err != nil { if err != nil {
logs.Println("[NODE]start failed: read node config failed: " + err.Error()) logs.Println("[NODE]start failed: read node config failed: " + err.Error())
return return
} }
sharedNodeConfig = nodeConfig
// 设置rlimit // 设置rlimit
_ = utils.SetRLimit(1024 * 1024) _ = utils.SetRLimit(1024 * 1024)
@@ -37,3 +54,59 @@ func (this *Node) Start() {
// hold住进程 // hold住进程
<-stop <-stop
} }
// 读取API配置
func (this *Node) syncConfig(isFirstTime bool) error {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return errors.New("[NODE]create rpc client failed: " + err.Error())
}
configResp, err := rpcClient.NodeRPC().ComposeNodeConfig(rpcClient.Context(), &pb.ComposeNodeConfigRequest{})
if err != nil {
return errors.New("[NODE]read config from rpc failed: " + err.Error())
}
configBytes := configResp.ConfigJSON
nodeConfig := &configs.NodeConfig{}
err = json.Unmarshal(configBytes, nodeConfig)
if err != nil {
return errors.New("[NODE]decode config failed: " + err.Error())
}
// 写入到文件中
err = nodeConfig.Save()
if err != nil {
return err
}
// 如果版本相同,则只是保存
if lastVersion == nodeConfig.Version {
return nil
}
lastVersion = nodeConfig.Version
// 刷新配置
err = configs.ReloadNodeConfig()
if err != nil {
return err
}
if !isFirstTime {
return sharedListenerManager.Start(nodeConfig)
}
return nil
}
// 启动同步计时器
func (this *Node) startSyncTimer() {
ticker := time.NewTicker(60 * time.Second)
go func() {
for range ticker.C {
err := this.syncConfig(false)
if err != nil {
logs.Println("[NODE]sync config error: " + err.Error())
continue
}
}
}()
}

View File

@@ -0,0 +1,24 @@
package nodes
// 节点状态
type NodeStatus struct {
Version string `json:"version"`
Hostname string `json:"hostname"`
HostIP string `json:"hostIP"`
CPUUsage float64 `json:"cpuUsage"`
CPULogicalCount int `json:"cpuLogicalCount"`
CPUPhysicalCount int `json:"cpuPhysicalCount"`
MemoryUsage float64 `json:"memoryUsage"`
MemoryTotal uint64 `json:"memoryTotal"`
DiskUsage float64 `json:"diskUsage"`
DiskMaxUsage float64 `json:"diskMaxUsage"`
DiskMaxUsagePartition string `json:"diskMaxUsagePartition"`
DiskTotal uint64 `json:"diskTotal"`
UpdatedAt int64 `json:"updatedAt"`
Load1m float64 `json:"load1m"`
Load5m float64 `json:"load5m"`
Load15m float64 `json:"load15m"`
IsActive bool `json:"isActive"`
Error string `json:"error"`
}

View File

@@ -0,0 +1,172 @@
package nodes
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/rpc/pb"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"os"
"runtime"
"strings"
"time"
)
type NodeStatusExecutor struct {
isFirstTime bool
cpuUpdatedTime time.Time
cpuLogicalCount int
cpuPhysicalCount int
}
func NewNodeStatusExecutor() *NodeStatusExecutor {
return &NodeStatusExecutor{}
}
func (this *NodeStatusExecutor) Listen() {
this.isFirstTime = true
this.cpuUpdatedTime = time.Now()
this.update()
ticker := time.NewTicker(60 * time.Second)
for range ticker.C {
this.isFirstTime = false
this.update()
}
}
func (this *NodeStatusExecutor) update() {
status := &NodeStatus{}
status.Version = teaconst.Version
status.IsActive = true
hostname, _ := os.Hostname()
status.Hostname = hostname
this.updateCPU(status)
this.updateMem(status)
this.updateLoad(status)
this.updateDisk(status)
status.UpdatedAt = time.Now().Unix()
// 发送数据
jsonData, err := json.Marshal(status)
if err != nil {
logs.Println("[NODE]serial NodeStatus fail: " + err.Error())
return
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
logs.Println("[NODE]failed to open rpc: " + err.Error())
return
}
_, err = rpcClient.NodeRPC().UpdateNodeStatus(rpcClient.Context(), &pb.UpdateNodeStatusRequest{
StatusJSON: jsonData,
})
if err != nil {
logs.Println("[NODE]rpc UpdateNodeStatus() failed: " + err.Error())
return
}
}
// 更新CPU
func (this *NodeStatusExecutor) updateCPU(status *NodeStatus) {
duration := time.Duration(0)
if this.isFirstTime {
duration = 100 * time.Millisecond
}
percents, err := cpu.Percent(duration, false)
if err != nil {
status.Error = err.Error()
return
}
if len(percents) == 0 {
return
}
status.CPUUsage = percents[0] / 100
if time.Since(this.cpuUpdatedTime) > 300*time.Second { // 每隔5分钟才会更新一次
this.cpuUpdatedTime = time.Now()
status.CPULogicalCount, err = cpu.Counts(true)
if err != nil {
status.Error = err.Error()
return
}
status.CPUPhysicalCount, err = cpu.Counts(false)
if err != nil {
status.Error = err.Error()
return
}
this.cpuLogicalCount = status.CPULogicalCount
this.cpuPhysicalCount = status.CPUPhysicalCount
} else {
status.CPULogicalCount = this.cpuLogicalCount
status.CPUPhysicalCount = this.cpuPhysicalCount
}
}
// 更新硬盘
func (this *NodeStatusExecutor) updateDisk(status *NodeStatus) {
partitions, err := disk.Partitions(false)
if err != nil {
logs.Error(err)
return
}
lists.Sort(partitions, func(i int, j int) bool {
p1 := partitions[i]
p2 := partitions[j]
return p1.Mountpoint > p2.Mountpoint
})
// 当前TeaWeb所在的fs
rootFS := ""
rootTotal := uint64(0)
if lists.ContainsString([]string{"darwin", "linux", "freebsd"}, runtime.GOOS) {
for _, p := range partitions {
if p.Mountpoint == "/" {
rootFS = p.Fstype
usage, _ := disk.Usage(p.Mountpoint)
if usage != nil {
rootTotal = usage.Total
}
break
}
}
}
total := rootTotal
totalUsage := uint64(0)
maxUsage := float64(0)
for _, partition := range partitions {
if runtime.GOOS != "windows" && !strings.Contains(partition.Device, "/") && !strings.Contains(partition.Device, "\\") {
continue
}
// 跳过不同fs的
if len(rootFS) > 0 && rootFS != partition.Fstype {
continue
}
usage, err := disk.Usage(partition.Mountpoint)
if err != nil {
continue
}
if partition.Mountpoint != "/" && (usage.Total != rootTotal || total == 0) {
total += usage.Total
}
totalUsage += usage.Used
if usage.UsedPercent >= maxUsage {
maxUsage = usage.UsedPercent
status.DiskMaxUsagePartition = partition.Mountpoint
}
}
status.DiskTotal = total
status.DiskUsage = float64(totalUsage) / float64(total)
status.DiskMaxUsage = maxUsage / 100
}

View File

@@ -0,0 +1,40 @@
// +build !windows
package nodes
import (
"github.com/shirou/gopsutil/load"
"github.com/shirou/gopsutil/mem"
)
// 更新内存
func (this *NodeStatusExecutor) updateMem(status *NodeStatus) {
stat, err := mem.VirtualMemory()
if err != nil {
return
}
// 重新计算内存
if stat.Total > 0 {
stat.Used = stat.Total - stat.Free - stat.Buffers - stat.Cached
status.MemoryUsage = float64(stat.Used) / float64(stat.Total)
}
status.MemoryTotal = stat.Total
}
// 更新负载
func (this *NodeStatusExecutor) updateLoad(status *NodeStatus) {
stat, err := load.Avg()
if err != nil {
status.Error = err.Error()
return
}
if stat == nil {
status.Error = "load is nil"
return
}
status.Load1m = stat.Load1
status.Load5m = stat.Load5
status.Load15m = stat.Load15
}

View File

@@ -0,0 +1,101 @@
// +build windows
package agent
import (
"context"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/mem"
"math"
"sync"
"time"
)
type WindowsLoadValue struct {
Timestamp int64
Value int
}
var windowsLoadValues = []*WindowsLoadValue{}
var windowsLoadLocker = &sync.Mutex{}
// 更新内存
func (this *NodeStatusExecutor) updateMem(status *NodeStatus) {
stat, err := mem.VirtualMemory()
if err != nil {
status.Error = err.Error()
return
}
status.MemoryUsage = stat.UsedPercent
status.MemoryTotal = stat.Total
}
// 更新负载
func (this *NodeStatusExecutor) updateLoad(status *NodeStatus) {
timestamp := time.Now().Unix()
currentLoad := 0
info, err := cpu.ProcInfo()
if err == nil && len(info) > 0 && info[0].ProcessorQueueLength < 1000 {
currentLoad = int(info[0].ProcessorQueueLength)
}
// 删除15分钟之前的数据
windowsLoadLocker.Lock()
result := []*WindowsLoadValue{}
for _, v := range windowsLoadValues {
if timestamp-v.Timestamp > 15*60 {
continue
}
result = append(result, v)
}
result = append(result, &WindowsLoadValue{
Timestamp: timestamp,
Value: currentLoad,
})
windowsLoadValues = result
total1 := 0
count1 := 0
total5 := 0
count5 := 0
total15 := 0
count15 := 0
for _, v := range result {
if timestamp-v.Timestamp <= 60 {
total1 += v.Value
count1++
}
if timestamp-v.Timestamp <= 300 {
total5 += v.Value
count5++
}
total15 += v.Value
count15++
}
load1 := float64(0)
load5 := float64(0)
load15 := float64(0)
if count1 > 0 {
load1 = math.Round(float64(total1*100)/float64(count1)) / 100
}
if count5 > 0 {
load5 = math.Round(float64(total5*100)/float64(count5)) / 100
}
if count15 > 0 {
load15 = math.Round(float64(total15*100)/float64(count15)) / 100
}
windowsLoadLocker.Unlock()
// 在老Windows上不显示错误
if err == context.DeadlineExceeded {
err = nil
}
status.Load1m = load1
status.Load5m = load5
status.Load15m = load15
}

Some files were not shown because too many files have changed in this diff Show More