Initial commit (code only without large binaries)
This commit is contained in:
5
EdgeReporter/README.md
Normal file
5
EdgeReporter/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# EdgeReporter
|
||||
|
||||
## 特性
|
||||
* 跨平台,支持Linux、Windows、MacOS等
|
||||
* 简单易用,下载一个文件即可启动
|
||||
11
EdgeReporter/build/build-all.sh
Normal file
11
EdgeReporter/build/build-all.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
./build.sh linux amd64
|
||||
./build.sh linux 386
|
||||
#./build.sh linux arm64
|
||||
./build.sh linux mips64
|
||||
./build.sh linux mips64le
|
||||
./build.sh darwin amd64
|
||||
#./build.sh darwin arm64
|
||||
./build.sh windows amd64
|
||||
./build.sh windows 386
|
||||
71
EdgeReporter/build/build.sh
Normal file
71
EdgeReporter/build/build.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname $0)
|
||||
NAME="edge-reporter"
|
||||
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
EXE=""
|
||||
|
||||
if [ -z $OS ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
if [ -z $ARCH ]; then
|
||||
echo "usage: build.sh OS ARCH"
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$OS" == "windows" ]; then
|
||||
EXE=".exe"
|
||||
fi
|
||||
|
||||
echo "checking ..."
|
||||
ZIP_PATH=$(which zip)
|
||||
if [ -z $ZIP_PATH ]; then
|
||||
echo "we need 'zip' command to compress files"
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "copying ..."
|
||||
if [ ! -d $DIST ]; then
|
||||
mkdir $DIST
|
||||
mkdir $DIST/bin
|
||||
mkdir $DIST/configs
|
||||
mkdir $DIST/logs
|
||||
fi
|
||||
cp $ROOT/configs/api_reporter.template.yaml $DIST/configs
|
||||
|
||||
echo "building v${VERSION}/${OS}/${ARCH} ..."
|
||||
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
|
||||
|
||||
env GOOS=${OS} GOARCH=${ARCH} go build -trimpath -o $DIST/bin/${NAME}${EXE} -ldflags="-s -w" $ROOT/../cmd/edge-reporter/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"
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat $FILE)
|
||||
re="Version[ ]+=[ ]+\"([0-9.]+)\""
|
||||
if [[ $VERSION_DATA =~ $re ]]; then
|
||||
VERSION=${BASH_REMATCH[1]}
|
||||
echo $VERSION
|
||||
else
|
||||
echo "could not match version"
|
||||
exit
|
||||
fi
|
||||
}
|
||||
|
||||
build $1 $2
|
||||
2
EdgeReporter/build/configs/.gitignore
vendored
Normal file
2
EdgeReporter/build/configs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
api.yaml
|
||||
api_reporter.yaml
|
||||
3
EdgeReporter/build/configs/api_reporter.template.yaml
Normal file
3
EdgeReporter/build/configs/api_reporter.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "http://127.0.0.1:8003" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
1
EdgeReporter/build/logs/.gitignore
vendored
Normal file
1
EdgeReporter/build/logs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.log
|
||||
36
EdgeReporter/cmd/edge-reporter/main.go
Normal file
36
EdgeReporter/cmd/edge-reporter/main.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/apps"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/nodes"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|service|daemon]")
|
||||
app.On("daemon", func() {
|
||||
nodes.NewReportNode().Daemon()
|
||||
})
|
||||
app.On("service", func() {
|
||||
err := nodes.NewReportNode().InstallSystemService()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]install failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("run", func() {
|
||||
reporterNode := nodes.NewReportNode()
|
||||
reporterNode.Run()
|
||||
})
|
||||
app.Run(func() {
|
||||
reporterNode := nodes.NewReportNode()
|
||||
reporterNode.Run()
|
||||
})
|
||||
}
|
||||
2
EdgeReporter/dist/.gitignore
vendored
Normal file
2
EdgeReporter/dist/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.zip
|
||||
edge-reporter
|
||||
24
EdgeReporter/go.mod
Normal file
24
EdgeReporter/go.mod
Normal file
@@ -0,0 +1,24 @@
|
||||
module github.com/TeaOSLab/EdgeReporter
|
||||
|
||||
go 1.25
|
||||
|
||||
toolchain go1.25.0
|
||||
|
||||
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
|
||||
require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
|
||||
golang.org/x/sys v0.38.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
)
|
||||
42
EdgeReporter/go.sum
Normal file
42
EdgeReporter/go.sum
Normal file
@@ -0,0 +1,42 @@
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
|
||||
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 h1:DKU1r6Tj5s1vlU/moGhuGz7E3xRfwjdAfDzbsaQJtEY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
239
EdgeReporter/internal/apps/app_cmd.go
Normal file
239
EdgeReporter/internal/apps/app_cmd.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AppCmd App命令帮助
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usage string
|
||||
options []*CommandHelpOption
|
||||
appendStrings []string
|
||||
|
||||
directives []*Directive
|
||||
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
type CommandHelpOption struct {
|
||||
Code string
|
||||
Description string
|
||||
}
|
||||
|
||||
// Product 产品
|
||||
func (this *AppCmd) Product(product string) *AppCmd {
|
||||
this.product = product
|
||||
return this
|
||||
}
|
||||
|
||||
// Version 版本
|
||||
func (this *AppCmd) Version(version string) *AppCmd {
|
||||
this.version = version
|
||||
return this
|
||||
}
|
||||
|
||||
// Usage 使用方法
|
||||
func (this *AppCmd) Usage(usage string) *AppCmd {
|
||||
this.usage = usage
|
||||
return this
|
||||
}
|
||||
|
||||
// Option 选项
|
||||
func (this *AppCmd) Option(code string, description string) *AppCmd {
|
||||
this.options = append(this.options, &CommandHelpOption{
|
||||
Code: code,
|
||||
Description: description,
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
// Append 附加内容
|
||||
func (this *AppCmd) Append(appendString string) *AppCmd {
|
||||
this.appendStrings = append(this.appendStrings, appendString)
|
||||
return this
|
||||
}
|
||||
|
||||
// Print 打印
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On 添加指令
|
||||
func (this *AppCmd) On(arg string, callback func()) {
|
||||
this.directives = append(this.directives, &Directive{
|
||||
Arg: arg,
|
||||
Callback: callback,
|
||||
})
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
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
|
||||
}
|
||||
|
||||
// 日志
|
||||
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() {
|
||||
var pid = this.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(this.product+" already started, pid:", 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() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
|
||||
|
||||
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
|
||||
}
|
||||
|
||||
// 重启
|
||||
func (this *AppCmd) runRestart() {
|
||||
this.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
this.runStart()
|
||||
}
|
||||
|
||||
// 状态
|
||||
func (this *AppCmd) runStatus() {
|
||||
var pid = this.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(this.product + " not started yet")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(this.product + " is running, pid: " + types.String(pid))
|
||||
}
|
||||
|
||||
// 获取当前的PID
|
||||
func (this *AppCmd) getPID() int {
|
||||
if !this.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
6
EdgeReporter/internal/apps/directive.go
Normal file
6
EdgeReporter/internal/apps/directive.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package apps
|
||||
|
||||
type Directive struct {
|
||||
Arg string
|
||||
Callback func()
|
||||
}
|
||||
108
EdgeReporter/internal/apps/log_writer.go
Normal file
108
EdgeReporter/internal/apps/log_writer.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LogWriter struct {
|
||||
fp *os.File
|
||||
c chan string
|
||||
}
|
||||
|
||||
func (this *LogWriter) Init() {
|
||||
// 创建目录
|
||||
var dir = files.NewFile(Tea.LogDir())
|
||||
if !dir.Exists() {
|
||||
err := dir.Mkdir()
|
||||
if err != nil {
|
||||
log.Println("[LOG]create log dir failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 打开要写入的日志文件
|
||||
var logPath = Tea.LogFile("run.log")
|
||||
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
log.Println("[LOG]open log file failed: " + err.Error())
|
||||
} else {
|
||||
this.fp = fp
|
||||
}
|
||||
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
stat, err := fp.Stat()
|
||||
if err == nil {
|
||||
totalSize = stat.Size()
|
||||
}
|
||||
|
||||
for message := range this.c {
|
||||
totalSize += int64(len(message))
|
||||
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
|
||||
if err != nil {
|
||||
log.Println("[LOG]write log failed: " + err.Error())
|
||||
} else {
|
||||
// 如果太大则Truncate
|
||||
if totalSize > maxFileSize {
|
||||
_ = fp.Truncate(0)
|
||||
totalSize = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (this *LogWriter) Write(message string) {
|
||||
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
|
||||
if backgroundEnv != "on" {
|
||||
// 文件和行号
|
||||
var file string
|
||||
var line int
|
||||
if Tea.IsTesting() {
|
||||
var callDepth = 3
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(callDepth)
|
||||
if ok {
|
||||
file = this.packagePath(file)
|
||||
}
|
||||
}
|
||||
|
||||
if len(file) > 0 {
|
||||
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
|
||||
} else {
|
||||
log.Println(message)
|
||||
}
|
||||
}
|
||||
|
||||
this.c <- message
|
||||
}
|
||||
|
||||
func (this *LogWriter) Close() {
|
||||
if this.fp != nil {
|
||||
_ = this.fp.Close()
|
||||
}
|
||||
|
||||
close(this.c)
|
||||
}
|
||||
|
||||
func (this *LogWriter) packagePath(path string) string {
|
||||
var pieces = strings.Split(path, "/")
|
||||
if len(pieces) >= 2 {
|
||||
return strings.Join(pieces[len(pieces)-2:], "/")
|
||||
}
|
||||
return path
|
||||
}
|
||||
102
EdgeReporter/internal/configs/api_config.go
Normal file
102
EdgeReporter/internal/configs/api_config.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
var SharedAPIConfig *APIConfig
|
||||
|
||||
const ConfigFileName = "api_reporter.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
// APIConfig API配置
|
||||
type APIConfig struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
NodeId string `yaml:"nodeId"`
|
||||
Secret string `yaml:"secret"`
|
||||
NumberId int64 `yaml:"numberId"`
|
||||
}
|
||||
|
||||
// UpdateAPIConfig 修改API配置
|
||||
func UpdateAPIConfig(config *APIConfig) {
|
||||
SharedAPIConfig = config
|
||||
}
|
||||
|
||||
// LoadAPIConfig 加载API配置
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
if SharedAPIConfig != nil {
|
||||
return SharedAPIConfig, nil
|
||||
}
|
||||
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
if filename == oldConfigFileName {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
SharedAPIConfig = config
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("no config file '" + ConfigFileName + "' found")
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile 写入API配置
|
||||
func (this *APIConfig) WriteFile(path string) error {
|
||||
data, err := yaml.Marshal(this)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, data, 0666)
|
||||
}
|
||||
21
EdgeReporter/internal/configs/api_config_test.go
Normal file
21
EdgeReporter/internal/configs/api_config_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadAPIConfig(t *testing.T) {
|
||||
config, err := LoadAPIConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
18
EdgeReporter/internal/const/const.go
Normal file
18
EdgeReporter/internal/const/const.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "0.1.5"
|
||||
|
||||
ProductName = "Edge Reporter"
|
||||
ProcessName = "edge-reporter"
|
||||
ProductNameZH = "Edge"
|
||||
|
||||
Role = "report"
|
||||
|
||||
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
ErrServer = "服务器出了点小问题,请稍后重试"
|
||||
|
||||
SystemdServiceName = "edge-reporter"
|
||||
)
|
||||
41
EdgeReporter/internal/encrypt/magic_key.go
Normal file
41
EdgeReporter/internal/encrypt/magic_key.go
Normal 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
|
||||
}
|
||||
11
EdgeReporter/internal/encrypt/magic_key_test.go
Normal file
11
EdgeReporter/internal/encrypt/magic_key_test.go
Normal 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))
|
||||
}
|
||||
12
EdgeReporter/internal/encrypt/method.go
Normal file
12
EdgeReporter/internal/encrypt/method.go
Normal 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)
|
||||
}
|
||||
73
EdgeReporter/internal/encrypt/method_aes_128_cfb.go
Normal file
73
EdgeReporter/internal/encrypt/method_aes_128_cfb.go
Normal 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
|
||||
}
|
||||
92
EdgeReporter/internal/encrypt/method_aes_128_cfb_test.go
Normal file
92
EdgeReporter/internal/encrypt/method_aes_128_cfb_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
74
EdgeReporter/internal/encrypt/method_aes_192_cfb.go
Normal file
74
EdgeReporter/internal/encrypt/method_aes_192_cfb.go
Normal 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
|
||||
}
|
||||
45
EdgeReporter/internal/encrypt/method_aes_192_cfb_test.go
Normal file
45
EdgeReporter/internal/encrypt/method_aes_192_cfb_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
72
EdgeReporter/internal/encrypt/method_aes_256_cfb.go
Normal file
72
EdgeReporter/internal/encrypt/method_aes_256_cfb.go
Normal 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
|
||||
}
|
||||
42
EdgeReporter/internal/encrypt/method_aes_256_cfb_test.go
Normal file
42
EdgeReporter/internal/encrypt/method_aes_256_cfb_test.go
Normal 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))
|
||||
}
|
||||
26
EdgeReporter/internal/encrypt/method_raw.go
Normal file
26
EdgeReporter/internal/encrypt/method_raw.go
Normal 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
|
||||
}
|
||||
23
EdgeReporter/internal/encrypt/method_raw_test.go
Normal file
23
EdgeReporter/internal/encrypt/method_raw_test.go
Normal 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))
|
||||
}
|
||||
43
EdgeReporter/internal/encrypt/method_utils.go
Normal file
43
EdgeReporter/internal/encrypt/method_utils.go
Normal 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
|
||||
}
|
||||
8
EdgeReporter/internal/encrypt/method_utils_test.go
Normal file
8
EdgeReporter/internal/encrypt/method_utils_test.go
Normal 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", ""))
|
||||
}
|
||||
9
EdgeReporter/internal/events/events.go
Normal file
9
EdgeReporter/internal/events/events.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package events
|
||||
|
||||
type Event = string
|
||||
|
||||
const (
|
||||
EventStart Event = "start" // start loading
|
||||
EventLoad Event = "load" // 已加载
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
)
|
||||
27
EdgeReporter/internal/events/utils.go
Normal file
27
EdgeReporter/internal/events/utils.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package events
|
||||
|
||||
import "sync"
|
||||
|
||||
var eventsMap = map[string][]func(){} // event => []callbacks
|
||||
var locker = sync.Mutex{}
|
||||
|
||||
// 增加事件回调
|
||||
func On(event string, callback func()) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
callbacks, _ := eventsMap[event]
|
||||
callbacks = append(callbacks, callback)
|
||||
eventsMap[event] = callbacks
|
||||
}
|
||||
|
||||
// 通知事件
|
||||
func Notify(event string) {
|
||||
locker.Lock()
|
||||
callbacks, _ := eventsMap[event]
|
||||
locker.Unlock()
|
||||
|
||||
for _, callback := range callbacks {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
16
EdgeReporter/internal/events/utils_test.go
Normal file
16
EdgeReporter/internal/events/utils_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package events
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestOn(t *testing.T) {
|
||||
On("hello", func() {
|
||||
t.Log("world")
|
||||
})
|
||||
On("hello", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
On("hello2", func() {
|
||||
t.Log("world2")
|
||||
})
|
||||
Notify("hello")
|
||||
}
|
||||
12
EdgeReporter/internal/goman/instance.go
Normal file
12
EdgeReporter/internal/goman/instance.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import "time"
|
||||
|
||||
type Instance struct {
|
||||
Id uint64
|
||||
CreatedTime time.Time
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
81
EdgeReporter/internal/goman/lib.go
Normal file
81
EdgeReporter/internal/goman/lib.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var locker = &sync.Mutex{}
|
||||
var instanceMap = map[uint64]*Instance{} // id => *Instance
|
||||
var instanceId = uint64(0)
|
||||
|
||||
// New 新创建goroutine
|
||||
func New(f func()) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f()
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// NewWithArgs 创建带有参数的goroutine
|
||||
func NewWithArgs(f func(args ...interface{}), args ...interface{}) {
|
||||
_, file, line, _ := runtime.Caller(1)
|
||||
|
||||
go func() {
|
||||
locker.Lock()
|
||||
instanceId++
|
||||
|
||||
var instance = &Instance{
|
||||
Id: instanceId,
|
||||
CreatedTime: time.Now(),
|
||||
}
|
||||
|
||||
instance.File = file
|
||||
instance.Line = line
|
||||
|
||||
instanceMap[instanceId] = instance
|
||||
locker.Unlock()
|
||||
|
||||
// run function
|
||||
f(args...)
|
||||
|
||||
locker.Lock()
|
||||
delete(instanceMap, instanceId)
|
||||
locker.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
// List 列出所有正在运行goroutine
|
||||
func List() []*Instance {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
var result = []*Instance{}
|
||||
for _, instance := range instanceMap {
|
||||
result = append(result, instance)
|
||||
}
|
||||
return result
|
||||
}
|
||||
28
EdgeReporter/internal/goman/lib_test.go
Normal file
28
EdgeReporter/internal/goman/lib_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
177
EdgeReporter/internal/nodes/api_stream.go
Normal file
177
EdgeReporter/internal/nodes/api_stream.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/events"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type APIStream struct {
|
||||
stream pb.ReportNodeService_ReportNodeStreamClient
|
||||
|
||||
isQuiting bool
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
func NewAPIStream() *APIStream {
|
||||
return &APIStream{}
|
||||
}
|
||||
|
||||
func (this *APIStream) Start() {
|
||||
events.On(events.EventQuit, func() {
|
||||
this.isQuiting = true
|
||||
if this.cancelFunc != nil {
|
||||
this.cancelFunc()
|
||||
}
|
||||
})
|
||||
for {
|
||||
if this.isQuiting {
|
||||
return
|
||||
}
|
||||
err := this.loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("API_STREAM", err.Error())
|
||||
time.Sleep(10 * time.Second)
|
||||
continue
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *APIStream) loop() error {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(rpcClient.Context())
|
||||
this.cancelFunc = cancelFunc
|
||||
defer func() {
|
||||
cancelFunc()
|
||||
}()
|
||||
|
||||
nodeStream, err := rpcClient.ReportNodeRPC().ReportNodeStream(ctx)
|
||||
if err != nil {
|
||||
if this.isQuiting {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
this.stream = nodeStream
|
||||
|
||||
for {
|
||||
if this.isQuiting {
|
||||
logs.Println("API_STREAM", "quit")
|
||||
break
|
||||
}
|
||||
|
||||
message, err := nodeStream.Recv()
|
||||
if err != nil {
|
||||
if this.isQuiting {
|
||||
remotelogs.Println("API_STREAM", "quit")
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
switch message.Code {
|
||||
case reporterconfigs.MessageCodeConnectedAPINode: // 连接API节点成功
|
||||
err = this.handleConnectedAPINode(message)
|
||||
case reporterconfigs.MessageCodeNewNodeTask: // 有新的任务
|
||||
err = this.handleNewNodeTask(message)
|
||||
case reporterconfigs.MessageCodeCheckSystemdService: // 检查Systemd服务
|
||||
err = this.handleCheckSystemdService(message)
|
||||
default:
|
||||
err = this.handleUnknownMessage(message)
|
||||
}
|
||||
if err != nil {
|
||||
remotelogs.Error("API_STREAM", "handle message failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 连接API节点成功
|
||||
func (this *APIStream) handleConnectedAPINode(message *pb.ReportNodeStreamMessage) error {
|
||||
// 更改连接的APINode信息
|
||||
if len(message.DataJSON) == 0 {
|
||||
return nil
|
||||
}
|
||||
msg := &messageconfigs.ConnectedAPINodeMessage{}
|
||||
err := json.Unmarshal(message.DataJSON, msg)
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
|
||||
remotelogs.Println("API_STREAM", "connected to api node '"+strconv.FormatInt(msg.APINodeId, 10)+"'")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理配置变化
|
||||
func (this *APIStream) handleNewNodeTask(message *pb.ReportNodeStreamMessage) error {
|
||||
select {
|
||||
case nodeTaskNotify <- true:
|
||||
default:
|
||||
|
||||
}
|
||||
this.replyOk(message.RequestId, "ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查Systemd服务
|
||||
func (this *APIStream) handleCheckSystemdService(message *pb.ReportNodeStreamMessage) error {
|
||||
systemctl, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "'systemctl' not found")
|
||||
return nil
|
||||
}
|
||||
if len(systemctl) == 0 {
|
||||
this.replyFail(message.RequestId, "'systemctl' not found")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := utils.NewCommandExecutor()
|
||||
shortName := teaconst.SystemdServiceName
|
||||
cmd.Add(systemctl, "is-enabled", shortName)
|
||||
output, err := cmd.Run()
|
||||
if err != nil {
|
||||
this.replyFail(message.RequestId, "'systemctl' command error: "+err.Error())
|
||||
return nil
|
||||
}
|
||||
if output == "enabled" {
|
||||
this.replyOk(message.RequestId, "ok")
|
||||
} else {
|
||||
this.replyFail(message.RequestId, "not installed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理未知消息
|
||||
func (this *APIStream) handleUnknownMessage(message *pb.ReportNodeStreamMessage) error {
|
||||
this.replyFail(message.RequestId, "unknown message code '"+message.Code+"'")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 回复失败
|
||||
func (this *APIStream) replyFail(requestId int64, message string) {
|
||||
_ = this.stream.Send(&pb.ReportNodeStreamMessage{RequestId: requestId, IsOk: false, Message: message})
|
||||
}
|
||||
|
||||
// 回复成功
|
||||
func (this *APIStream) replyOk(requestId int64, message string) {
|
||||
_ = this.stream.Send(&pb.ReportNodeStreamMessage{RequestId: requestId, IsOk: true, Message: message})
|
||||
}
|
||||
251
EdgeReporter/internal/nodes/report_node.go
Normal file
251
EdgeReporter/internal/nodes/report_node.go
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/events"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedNodeConfig *reporterconfigs.NodeConfig
|
||||
var nodeTaskNotify = make(chan bool, 8)
|
||||
|
||||
type ReportNode struct {
|
||||
sock *gosock.Sock
|
||||
|
||||
RPC *rpc.RPCClient
|
||||
}
|
||||
|
||||
func NewReportNode() *ReportNode {
|
||||
return &ReportNode{
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ReportNode) Run() {
|
||||
// 本地Sock
|
||||
err := this.listenSock()
|
||||
if err != nil {
|
||||
logs.Println("[REPORT_NODE]" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
events.Notify(events.EventStart)
|
||||
|
||||
// 启动
|
||||
go this.start()
|
||||
|
||||
// Hold住进程
|
||||
select {}
|
||||
}
|
||||
|
||||
// Daemon 守护程序
|
||||
func (this *ReportNode) Daemon() {
|
||||
path := os.TempDir() + "/edge-reporter.sock"
|
||||
isDebug := lists.ContainsString(os.Args, "debug")
|
||||
isDebug = true
|
||||
for {
|
||||
conn, err := net.DialTimeout("unix", path, 1*time.Second)
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]starting ...")
|
||||
}
|
||||
|
||||
// 尝试启动
|
||||
err = func() error {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(exe)
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
if isDebug {
|
||||
log.Println("[DAEMON]", err)
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
} else {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
} else {
|
||||
_ = conn.Close()
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InstallSystemService 安装系统服务
|
||||
func (this *ReportNode) InstallSystemService() error {
|
||||
shortName := teaconst.SystemdServiceName
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manager := utils.NewServiceManager(shortName, teaconst.ProductName)
|
||||
err = manager.Install(exe, []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 监听本地sock
|
||||
func (this *ReportNode) listenSock() error {
|
||||
if runtime.GOOS == "windows" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查是否在运行
|
||||
if this.sock.IsListening() {
|
||||
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err == nil {
|
||||
return errors.New("error: the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
|
||||
} else {
|
||||
return errors.New("error: the process is already running")
|
||||
}
|
||||
}
|
||||
|
||||
// 启动监听
|
||||
go func() {
|
||||
this.sock.OnCommand(func(cmd *gosock.Command) {
|
||||
switch cmd.Code {
|
||||
case "pid":
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "pid",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
},
|
||||
})
|
||||
case "info":
|
||||
exePath, _ := os.Executable()
|
||||
_ = cmd.Reply(&gosock.Command{
|
||||
Code: "info",
|
||||
Params: map[string]interface{}{
|
||||
"pid": os.Getpid(),
|
||||
"version": teaconst.Version,
|
||||
"path": exePath,
|
||||
},
|
||||
})
|
||||
case "stop":
|
||||
_ = cmd.ReplyOk()
|
||||
|
||||
// 退出主进程
|
||||
events.Notify(events.EventQuit)
|
||||
os.Exit(0)
|
||||
}
|
||||
})
|
||||
|
||||
err := this.sock.Listen()
|
||||
if err != nil {
|
||||
logs.Println("REPORT_NODE", err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
logs.Println("REPORT_NODE", "quit unix sock")
|
||||
_ = this.sock.Close()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 启动
|
||||
func (this *ReportNode) start() {
|
||||
remotelogs.Println("REPORT_NODE", "starting ...")
|
||||
|
||||
apiConfig, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
remotelogs.Println("REPORT_NODE", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
client, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
remotelogs.Error("REPORT_NODE", err.Error())
|
||||
return
|
||||
}
|
||||
this.RPC = client
|
||||
tryTimes := 0
|
||||
var configJSON []byte
|
||||
for {
|
||||
resp, err := client.ReportNodeRPC().FindCurrentReportNodeConfig(client.Context(), &pb.FindCurrentReportNodeConfigRequest{})
|
||||
if err != nil {
|
||||
tryTimes++
|
||||
if tryTimes%10 == 0 {
|
||||
remotelogs.Error("NODE", err.Error())
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// 不做长时间的无意义的重试
|
||||
if tryTimes > 1000 {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
configJSON = resp.ReportNodeJSON
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(configJSON) == 0 {
|
||||
remotelogs.Error("NODE", "can not find node")
|
||||
return
|
||||
}
|
||||
var config = &reporterconfigs.NodeConfig{}
|
||||
err = json.Unmarshal(configJSON, config)
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "decode config failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
remotelogs.Error("NODE", "init config failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
sharedNodeConfig = config
|
||||
apiConfig.NumberId = config.Id
|
||||
|
||||
remotelogs.Println("REPORT_NODE", "started")
|
||||
|
||||
// 发送通知
|
||||
events.Notify(events.EventLoad)
|
||||
|
||||
// 连接API
|
||||
go NewAPIStream().Start()
|
||||
|
||||
// 状态上报
|
||||
go NewStatusManager().Start()
|
||||
|
||||
// 执行任务
|
||||
go NewTaskManager().Start()
|
||||
}
|
||||
70
EdgeReporter/internal/nodes/status_manager.go
Normal file
70
EdgeReporter/internal/nodes/status_manager.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sharedNodeStatus = &reporterconfigs.Status{}
|
||||
|
||||
type StatusManager struct {
|
||||
}
|
||||
|
||||
func NewStatusManager() *StatusManager {
|
||||
return &StatusManager{}
|
||||
}
|
||||
|
||||
func (this *StatusManager) Start() {
|
||||
var ticker = time.NewTicker(30 * time.Second)
|
||||
|
||||
// 先执行一次
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("STATUS_MANAGER", err.Error())
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("STATUS_MANAGER", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *StatusManager) Loop() error {
|
||||
sharedNodeStatus.BuildVersion = teaconst.Version
|
||||
sharedNodeStatus.BuildVersionCode = utils.VersionToLong(teaconst.Version)
|
||||
sharedNodeStatus.OS = runtime.GOOS
|
||||
sharedNodeStatus.OSName = runtime.GOOS // TODO 需要实现
|
||||
|
||||
u, err := user.Current()
|
||||
if err == nil && u != nil {
|
||||
sharedNodeStatus.Username = u.Name
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
statusJSON, err := json.Marshal(sharedNodeStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = rpcClient.ReportNodeRPC().UpdateReportNodeStatus(rpcClient.Context(), &pb.UpdateReportNodeStatusRequest{StatusJSON: statusJSON})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
3
EdgeReporter/internal/nodes/status_manager_darwin.go
Normal file
3
EdgeReporter/internal/nodes/status_manager_darwin.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
16
EdgeReporter/internal/nodes/status_manager_test.go
Normal file
16
EdgeReporter/internal/nodes/status_manager_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStatusManager_Loop(t *testing.T) {
|
||||
err := NewStatusManager().Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
141
EdgeReporter/internal/nodes/task_manager.go
Normal file
141
EdgeReporter/internal/nodes/task_manager.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/reporterconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/rpc"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TaskManager struct {
|
||||
}
|
||||
|
||||
func NewTaskManager() *TaskManager {
|
||||
return &TaskManager{}
|
||||
}
|
||||
|
||||
func (this *TaskManager) Start() {
|
||||
// 监控时间没必要过短,因为我们不需要那么敏感
|
||||
var ticker = time.NewTicker(3 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
ticker = time.NewTicker(1 * time.Minute)
|
||||
}
|
||||
|
||||
// 立即执行一次
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("TASK_MANAGER", err.Error())
|
||||
}
|
||||
|
||||
// 循环执行
|
||||
for range ticker.C {
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.Error("TASK_MANAGER", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TaskManager) Loop() error {
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := rpcClient.ReportNodeRPC().FindReportNodeTasks(rpcClient.Context(), &pb.FindReportNodeTasksRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
var tasksJSON = resp.IpAddrTasksJSON
|
||||
if len(tasksJSON) > 0 {
|
||||
var ipTasks = []*reporterconfigs.IPTask{}
|
||||
err = json.Unmarshal(tasksJSON, &ipTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(ipTasks) > 0 {
|
||||
var queue = make(chan *reporterconfigs.IPTask, len(ipTasks))
|
||||
for _, task := range ipTasks {
|
||||
queue <- task
|
||||
}
|
||||
|
||||
var pbResults = []*pb.ReportResult{}
|
||||
var concurrent = 32
|
||||
var wg = &sync.WaitGroup{}
|
||||
wg.Add(concurrent)
|
||||
for i := 0; i < concurrent; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for {
|
||||
select {
|
||||
case task := <-queue:
|
||||
desc, costMs, err := this.runIPTask(task)
|
||||
var errString = ""
|
||||
if err != nil {
|
||||
errString = err.Error()
|
||||
}
|
||||
|
||||
// 级别
|
||||
var level reporterconfigs.ReportLevel
|
||||
if err != nil {
|
||||
level = reporterconfigs.ReportLevelBroken
|
||||
} else if costMs > 3000 { //
|
||||
level = reporterconfigs.ReportLevelBad
|
||||
} else if costMs > 1000 {
|
||||
level = reporterconfigs.ReportLevelNormal
|
||||
} else {
|
||||
level = reporterconfigs.ReportLevelGood
|
||||
}
|
||||
pbResults = append(pbResults, &pb.ReportResult{
|
||||
Type: reporterconfigs.TaskTypeIPAddr,
|
||||
TargetId: task.AddrId,
|
||||
TargetDesc: desc,
|
||||
IsOk: err == nil,
|
||||
CostMs: float32(costMs),
|
||||
Error: errString,
|
||||
Level: level,
|
||||
})
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(pbResults) > 0 {
|
||||
_, err = rpcClient.ReportResultRPC().UpdateReportResults(rpcClient.Context(), &pb.UpdateReportResultsRequest{ReportResults: pbResults})
|
||||
if err != nil {
|
||||
remotelogs.Error("TASK_MANAGER", "upload report results failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *TaskManager) runIPTask(task *reporterconfigs.IPTask) (desc string, costMs float64, err error) {
|
||||
before := time.Now()
|
||||
|
||||
desc = configutils.QuoteIP(task.IP) + ":" + types.String(task.Port)
|
||||
conn, err := net.DialTimeout("tcp", desc, 5*time.Second)
|
||||
if err != nil {
|
||||
return desc, time.Since(before).Seconds() * 1000, err
|
||||
}
|
||||
_ = conn.Close()
|
||||
return desc, time.Since(before).Seconds() * 1000, nil
|
||||
}
|
||||
14
EdgeReporter/internal/nodes/task_manager_test.go
Normal file
14
EdgeReporter/internal/nodes/task_manager_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package nodes
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTaskManager_Loop(t *testing.T) {
|
||||
var manager = NewTaskManager()
|
||||
err := manager.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
120
EdgeReporter/internal/remotelogs/utils.go
Normal file
120
EdgeReporter/internal/remotelogs/utils.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package remotelogs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/rpc"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"time"
|
||||
)
|
||||
|
||||
var logChan = make(chan *pb.NodeLog, 64) // 队列数量不需要太长,因为日志通常仅仅为调试用
|
||||
|
||||
func init() {
|
||||
// 定期上传日志
|
||||
ticker := time.NewTicker(60 * time.Second)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
err := uploadLogs()
|
||||
if err != nil {
|
||||
logs.Println("[LOG]" + err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Println 打印普通信息
|
||||
func Println(tag string, description string) {
|
||||
logs.Println("[" + tag + "]" + description)
|
||||
|
||||
nodeConfig, _ := configs.LoadAPIConfig()
|
||||
if nodeConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case logChan <- &pb.NodeLog{
|
||||
Role: teaconst.Role,
|
||||
Tag: tag,
|
||||
Description: description,
|
||||
Level: "info",
|
||||
NodeId: nodeConfig.NumberId,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Warn 打印警告信息
|
||||
func Warn(tag string, description string) {
|
||||
logs.Println("[" + tag + "]" + description)
|
||||
|
||||
nodeConfig, _ := configs.LoadAPIConfig()
|
||||
if nodeConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case logChan <- &pb.NodeLog{
|
||||
Role: teaconst.Role,
|
||||
Tag: tag,
|
||||
Description: description,
|
||||
Level: "warning",
|
||||
NodeId: nodeConfig.NumberId,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Error 打印错误信息
|
||||
func Error(tag string, description string) {
|
||||
logs.Println("[" + tag + "]" + description)
|
||||
|
||||
nodeConfig, _ := configs.LoadAPIConfig()
|
||||
if nodeConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case logChan <- &pb.NodeLog{
|
||||
Role: teaconst.Role,
|
||||
Tag: tag,
|
||||
Description: description,
|
||||
Level: "error",
|
||||
NodeId: nodeConfig.NumberId,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}:
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 上传日志
|
||||
func uploadLogs() error {
|
||||
logList := []*pb.NodeLog{}
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case log := <-logChan:
|
||||
if log.NodeId == 0 {
|
||||
continue
|
||||
}
|
||||
logList = append(logList, log)
|
||||
default:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
if len(logList) == 0 {
|
||||
return nil
|
||||
}
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.NodeLogRPC().CreateNodeLogs(rpcClient.Context(), &pb.CreateNodeLogsRequest{NodeLogs: logList})
|
||||
return err
|
||||
}
|
||||
15
EdgeReporter/internal/remotelogs/utils_test.go
Normal file
15
EdgeReporter/internal/remotelogs/utils_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package remotelogs
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPrintln(t *testing.T) {
|
||||
Println("test", "123")
|
||||
err := uploadLogs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
191
EdgeReporter/internal/rpc/rpc_client.go
Normal file
191
EdgeReporter/internal/rpc/rpc_client.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/encrypt"
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RPCClient RPC客户端
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
|
||||
locker sync.Mutex
|
||||
}
|
||||
|
||||
// NewRPCClient 构造新的RPC客户端
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
client := &RPCClient{
|
||||
apiConfig: apiConfig,
|
||||
}
|
||||
|
||||
err := client.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (this *RPCClient) SysSettingRPC() pb.SysSettingServiceClient {
|
||||
return pb.NewSysSettingServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) SysLockerRPC() pb.SysLockerServiceClient {
|
||||
return pb.NewSysLockerServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) NodeLogRPC() pb.NodeLogServiceClient {
|
||||
return pb.NewNodeLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) APINodeRPC() pb.APINodeServiceClient {
|
||||
return pb.NewAPINodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportNodeRPC() pb.ReportNodeServiceClient {
|
||||
return pb.NewReportNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) ReportResultRPC() pb.ReportResultServiceClient {
|
||||
return pb.NewReportResultServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
// Context 构造用户上下文
|
||||
func (this *RPCClient) Context() context.Context {
|
||||
ctx := context.Background()
|
||||
m := maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "report",
|
||||
"userId": 0,
|
||||
}
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, this.apiConfig.Secret, this.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
data, err := method.Encrypt(m.AsJSON())
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
return context.Background()
|
||||
}
|
||||
token := base64.StdEncoding.EncodeToString(data)
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "nodeId", this.apiConfig.NodeId, "token", token)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// UpdateConfig 修改配置
|
||||
func (this *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
this.apiConfig = config
|
||||
|
||||
this.locker.Lock()
|
||||
err := this.init()
|
||||
this.locker.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func (this *RPCClient) init() error {
|
||||
// 重新连接
|
||||
var conns = []*grpc.ClientConn{}
|
||||
for _, endpoint := range this.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return errors.New("parse endpoint failed: " + err.Error())
|
||||
}
|
||||
var conn *grpc.ClientConn
|
||||
var callOptions = grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128*1024*1024),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 30 * time.Second,
|
||||
})
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(u.Host, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), callOptions, keepaliveParams)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
if len(conns) == 0 {
|
||||
return errors.New("[RPC]no available endpoints")
|
||||
}
|
||||
|
||||
// 这里不需要加锁,因为会和pickConn冲突
|
||||
this.conns = conns
|
||||
return nil
|
||||
}
|
||||
|
||||
// 随机选择一个连接
|
||||
func (this *RPCClient) pickConn() *grpc.ClientConn {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 检查连接状态
|
||||
var countConns = len(this.conns)
|
||||
if countConns > 0 {
|
||||
if countConns == 1 {
|
||||
return this.conns[0]
|
||||
}
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
var availableConns = []*grpc.ClientConn{}
|
||||
for _, conn := range this.conns {
|
||||
if conn.GetState() == state {
|
||||
availableConns = append(availableConns, conn)
|
||||
}
|
||||
}
|
||||
if len(availableConns) > 0 {
|
||||
return this.randConn(availableConns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.randConn(this.conns)
|
||||
}
|
||||
|
||||
func (this *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
var l = len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
5
EdgeReporter/internal/rpc/rpc_client_test.go
Normal file
5
EdgeReporter/internal/rpc/rpc_client_test.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
)
|
||||
30
EdgeReporter/internal/rpc/rpc_utils.go
Normal file
30
EdgeReporter/internal/rpc/rpc_utils.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/configs"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var sharedRPC *RPCClient = nil
|
||||
var locker = &sync.Mutex{}
|
||||
|
||||
func SharedRPC() (*RPCClient, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if sharedRPC != nil {
|
||||
return sharedRPC, nil
|
||||
}
|
||||
|
||||
config, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := NewRPCClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sharedRPC = client
|
||||
return sharedRPC, nil
|
||||
}
|
||||
7
EdgeReporter/internal/utils/command.go
Normal file
7
EdgeReporter/internal/utils/command.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
// 命令定义
|
||||
type Command struct {
|
||||
Name string
|
||||
Args []string
|
||||
}
|
||||
61
EdgeReporter/internal/utils/command_executor.go
Normal file
61
EdgeReporter/internal/utils/command_executor.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// 命令执行器
|
||||
type CommandExecutor struct {
|
||||
commands []*Command
|
||||
}
|
||||
|
||||
// 获取新对象
|
||||
func NewCommandExecutor() *CommandExecutor {
|
||||
return &CommandExecutor{}
|
||||
}
|
||||
|
||||
// 添加命令
|
||||
func (this *CommandExecutor) Add(command string, arg ...string) {
|
||||
this.commands = append(this.commands, &Command{
|
||||
Name: command,
|
||||
Args: arg,
|
||||
})
|
||||
}
|
||||
|
||||
// 执行命令
|
||||
func (this *CommandExecutor) Run() (output string, err error) {
|
||||
if len(this.commands) == 0 {
|
||||
return "", errors.New("no commands no run")
|
||||
}
|
||||
var lastCmd *exec.Cmd = nil
|
||||
var lastData []byte = nil
|
||||
for _, command := range this.commands {
|
||||
cmd := exec.Command(command.Name, command.Args...)
|
||||
stdout := bytes.NewBuffer([]byte{})
|
||||
cmd.Stdout = stdout
|
||||
if lastCmd != nil {
|
||||
cmd.Stdin = bytes.NewBuffer(lastData)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
_, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", err
|
||||
}
|
||||
lastData = stdout.Bytes()
|
||||
|
||||
lastCmd = cmd
|
||||
}
|
||||
|
||||
return string(bytes.TrimSpace(lastData)), nil
|
||||
}
|
||||
8
EdgeReporter/internal/utils/errors.go
Normal file
8
EdgeReporter/internal/utils/errors.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package utils
|
||||
|
||||
import "github.com/iwind/TeaGo/logs"
|
||||
|
||||
func PrintError(err error) {
|
||||
// TODO 记录调用的文件名、行数
|
||||
logs.Println("[ERROR]" + err.Error())
|
||||
}
|
||||
19
EdgeReporter/internal/utils/ip.go
Normal file
19
EdgeReporter/internal/utils/ip.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
// 将IP转换为整型
|
||||
func IP2Long(ip string) uint32 {
|
||||
s := net.ParseIP(ip)
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
if len(s) == 16 {
|
||||
return binary.BigEndian.Uint32(s[12:16])
|
||||
}
|
||||
return binary.BigEndian.Uint32(s)
|
||||
}
|
||||
111
EdgeReporter/internal/utils/service.go
Normal file
111
EdgeReporter/internal/utils/service.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 服务管理器
|
||||
type ServiceManager struct {
|
||||
Name string
|
||||
Description string
|
||||
|
||||
fp *os.File
|
||||
logger *log.Logger
|
||||
onceLocker sync.Once
|
||||
}
|
||||
|
||||
// 获取对象
|
||||
func NewServiceManager(name, description string) *ServiceManager {
|
||||
manager := &ServiceManager{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
// root
|
||||
manager.resetRoot()
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// 设置服务
|
||||
func (this *ServiceManager) setup() {
|
||||
this.onceLocker.Do(func() {
|
||||
logFile := files.NewFile(Tea.Root + "/logs/service.log")
|
||||
if logFile.Exists() {
|
||||
logFile.Delete()
|
||||
}
|
||||
|
||||
//logger
|
||||
fp, err := os.OpenFile(Tea.Root+"/logs/service.log", os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
return
|
||||
}
|
||||
this.fp = fp
|
||||
this.logger = log.New(fp, "", log.LstdFlags)
|
||||
})
|
||||
}
|
||||
|
||||
// 记录普通日志
|
||||
func (this *ServiceManager) Log(msg string) {
|
||||
this.setup()
|
||||
if this.logger == nil {
|
||||
return
|
||||
}
|
||||
this.logger.Println("[info]" + msg)
|
||||
}
|
||||
|
||||
// 记录错误日志
|
||||
func (this *ServiceManager) LogError(msg string) {
|
||||
this.setup()
|
||||
if this.logger == nil {
|
||||
return
|
||||
}
|
||||
this.logger.Println("[error]" + msg)
|
||||
}
|
||||
|
||||
// 关闭
|
||||
func (this *ServiceManager) Close() error {
|
||||
if this.fp != nil {
|
||||
return this.fp.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 重置Root
|
||||
func (this *ServiceManager) resetRoot() {
|
||||
if !Tea.IsTesting() {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
exePath = os.Args[0]
|
||||
}
|
||||
link, err := filepath.EvalSymlinks(exePath)
|
||||
if err == nil {
|
||||
exePath = link
|
||||
}
|
||||
fullPath, err := filepath.Abs(exePath)
|
||||
if err == nil {
|
||||
Tea.UpdateRoot(filepath.Dir(filepath.Dir(fullPath)))
|
||||
}
|
||||
}
|
||||
Tea.SetPublicDir(Tea.Root + Tea.DS + "web" + Tea.DS + "public")
|
||||
Tea.SetViewsDir(Tea.Root + Tea.DS + "web" + Tea.DS + "views")
|
||||
Tea.SetTmpDir(Tea.Root + Tea.DS + "web" + Tea.DS + "tmp")
|
||||
}
|
||||
|
||||
// 保持命令行窗口是打开的
|
||||
func (this *ServiceManager) PauseWindow() {
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
|
||||
b := make([]byte, 1)
|
||||
_, _ = os.Stdin.Read(b)
|
||||
}
|
||||
154
EdgeReporter/internal/utils/service_linux.go
Normal file
154
EdgeReporter/internal/utils/service_linux.go
Normal file
@@ -0,0 +1,154 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var systemdServiceFile = "/etc/systemd/system/edge-reporter.service"
|
||||
var initServiceFile = "/etc/init.d/" + teaconst.SystemdServiceName
|
||||
|
||||
// 安装服务
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can install the service")
|
||||
}
|
||||
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return this.installInitService(exePath, args)
|
||||
}
|
||||
|
||||
return this.installSystemdService(systemd, exePath, args)
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
func (this *ServiceManager) Start() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can start the service")
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return exec.Command(systemd, "start", teaconst.SystemdServiceName+".service").Start()
|
||||
}
|
||||
return exec.Command("service", teaconst.ProcessName, "start").Start()
|
||||
}
|
||||
|
||||
// 删除服务
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can uninstall the service")
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// disable service
|
||||
exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Start()
|
||||
|
||||
// reload
|
||||
exec.Command(systemd, "daemon-reload")
|
||||
|
||||
return files.NewFile(systemdServiceFile).Delete()
|
||||
}
|
||||
|
||||
f := files.NewFile(initServiceFile)
|
||||
if f.Exists() {
|
||||
return f.Delete()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// install init service
|
||||
func (this *ServiceManager) installInitService(exePath string, args []string) error {
|
||||
shortName := teaconst.SystemdServiceName
|
||||
scriptFile := Tea.Root + "/scripts/" + shortName
|
||||
if !files.NewFile(scriptFile).Exists() {
|
||||
return errors.New("'scripts/" + shortName + "' file not exists")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(scriptFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = regexp.MustCompile("INSTALL_DIR=.+").ReplaceAll(data, []byte("INSTALL_DIR="+Tea.Root))
|
||||
err = os.WriteFile(initServiceFile, data, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chkCmd, err := exec.LookPath("chkconfig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = exec.Command(chkCmd, "--add", teaconst.ProcessName).Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// install systemd service
|
||||
func (this *ServiceManager) installSystemdService(systemd, exePath string, args []string) error {
|
||||
shortName := teaconst.SystemdServiceName
|
||||
longName := "GoEdge Reporter" // TODO 将来可以修改
|
||||
|
||||
desc := `# Provides: ` + shortName + `
|
||||
# Required-Start: $all
|
||||
# Required-Stop:
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop:
|
||||
# Short-Description: ` + longName + ` Service
|
||||
### END INIT INFO
|
||||
|
||||
[Unit]
|
||||
Description=` + longName + ` Service
|
||||
Before=shutdown.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` reload
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target`
|
||||
|
||||
// write file
|
||||
err := os.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// stop current systemd service if running
|
||||
exec.Command(systemd, "stop", shortName+".service")
|
||||
|
||||
// reload
|
||||
exec.Command(systemd, "daemon-reload")
|
||||
|
||||
// enable
|
||||
cmd := exec.Command(systemd, "enable", shortName+".service")
|
||||
return cmd.Run()
|
||||
}
|
||||
19
EdgeReporter/internal/utils/service_others.go
Normal file
19
EdgeReporter/internal/utils/service_others.go
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build !linux && !windows
|
||||
// +build !linux,!windows
|
||||
|
||||
package utils
|
||||
|
||||
// 安装服务
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
func (this *ServiceManager) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 删除服务
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
return nil
|
||||
}
|
||||
12
EdgeReporter/internal/utils/service_test.go
Normal file
12
EdgeReporter/internal/utils/service_test.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServiceManager_Log(t *testing.T) {
|
||||
manager := NewServiceManager(teaconst.ProductName, teaconst.ProductName+" Server")
|
||||
manager.Log("Hello, World")
|
||||
manager.LogError("Hello, World")
|
||||
}
|
||||
175
EdgeReporter/internal/utils/service_windows.go
Normal file
175
EdgeReporter/internal/utils/service_windows.go
Normal file
@@ -0,0 +1,175 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeReporter/internal/const"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/mgr"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Install 安装服务
|
||||
func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
if err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf("service %s already exists", this.Name)
|
||||
}
|
||||
|
||||
s, err = m.CreateService(this.Name, exePath, mgr.Config{
|
||||
DisplayName: this.Name,
|
||||
Description: this.Description,
|
||||
StartType: windows.SERVICE_AUTO_START,
|
||||
}, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating: %s", err.Error())
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动服务
|
||||
func (this *ServiceManager) Start() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not access service: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
err = s.Start("service")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not start service: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uninstall 删除服务
|
||||
func (this *ServiceManager) Uninstall() error {
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connecting: %s please 'Run as administrator' again", err.Error())
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(this.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open service: %s", err.Error())
|
||||
}
|
||||
|
||||
// shutdown service
|
||||
_, err = s.Control(svc.Stop)
|
||||
if err != nil {
|
||||
fmt.Printf("shutdown service: %s\n", err.Error())
|
||||
}
|
||||
|
||||
defer s.Close()
|
||||
err = s.Delete()
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting: %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
func (this *ServiceManager) Run() {
|
||||
err := svc.Run(this.Name, this)
|
||||
if err != nil {
|
||||
this.LogError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Execute 同服务管理器的交互
|
||||
func (this *ServiceManager) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
|
||||
|
||||
changes <- svc.Status{
|
||||
State: svc.StartPending,
|
||||
}
|
||||
|
||||
changes <- svc.Status{
|
||||
State: svc.Running,
|
||||
Accepts: cmdsAccepted,
|
||||
}
|
||||
|
||||
// start service
|
||||
this.Log("start")
|
||||
this.cmdStart()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case c := <-r:
|
||||
switch c.Cmd {
|
||||
case svc.Interrogate:
|
||||
this.Log("cmd: Interrogate")
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
this.Log("cmd: Stop|Shutdown")
|
||||
|
||||
// stop service
|
||||
this.cmdStop()
|
||||
|
||||
break loop
|
||||
case svc.Pause:
|
||||
this.Log("cmd: Pause")
|
||||
|
||||
// stop service
|
||||
this.cmdStop()
|
||||
|
||||
changes <- svc.Status{
|
||||
State: svc.Paused,
|
||||
Accepts: cmdsAccepted,
|
||||
}
|
||||
case svc.Continue:
|
||||
this.Log("cmd: Continue")
|
||||
|
||||
// start service
|
||||
this.cmdStart()
|
||||
|
||||
changes <- svc.Status{
|
||||
State: svc.Running,
|
||||
Accepts: cmdsAccepted,
|
||||
}
|
||||
default:
|
||||
this.LogError(fmt.Sprintf("unexpected control request #%d\r\n", c))
|
||||
}
|
||||
}
|
||||
}
|
||||
changes <- svc.Status{
|
||||
State: svc.StopPending,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 启动Web服务
|
||||
func (this *ServiceManager) cmdStart() {
|
||||
cmd := exec.Command(Tea.Root+Tea.DS+"bin"+Tea.DS+teaconst.SystemdServiceName+".exe", "start")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
this.LogError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 停止Web服务
|
||||
func (this *ServiceManager) cmdStop() {
|
||||
cmd := exec.Command(Tea.Root+Tea.DS+"bin"+Tea.DS+teaconst.SystemdServiceName+".exe", "stop")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
this.LogError(err.Error())
|
||||
}
|
||||
}
|
||||
10
EdgeReporter/internal/utils/sizes/sizes.go
Normal file
10
EdgeReporter/internal/utils/sizes/sizes.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes
|
||||
|
||||
const (
|
||||
K int64 = 1024
|
||||
M = 1024 * K
|
||||
G = 1024 * M
|
||||
T = 1024 * G
|
||||
)
|
||||
17
EdgeReporter/internal/utils/sizes/sizes_test.go
Normal file
17
EdgeReporter/internal/utils/sizes/sizes_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package sizes_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeReporter/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSizes(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsTrue(sizes.K == 1024)
|
||||
a.IsTrue(sizes.M == 1024*1024)
|
||||
a.IsTrue(sizes.G == 1024*1024*1024)
|
||||
a.IsTrue(sizes.T == 1024*1024*1024*1024)
|
||||
}
|
||||
18
EdgeReporter/internal/utils/version.go
Normal file
18
EdgeReporter/internal/utils/version.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// VersionToLong 计算版本代号
|
||||
func VersionToLong(version string) uint32 {
|
||||
countDots := strings.Count(version, ".")
|
||||
if countDots == 2 {
|
||||
version += ".0"
|
||||
} else if countDots == 1 {
|
||||
version += ".0.0"
|
||||
} else if countDots == 0 {
|
||||
version += ".0.0.0"
|
||||
}
|
||||
return IP2Long(version)
|
||||
}
|
||||
Reference in New Issue
Block a user