Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View 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})
}

View 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()
}

View 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
}

View File

@@ -0,0 +1,3 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package nodes

View 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")
}

View 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
}

View 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")
}