管理端全部功能跑通

This commit is contained in:
robin
2026-02-27 10:35:22 +08:00
parent 4d275c921d
commit 150799f41d
263 changed files with 22664 additions and 4053 deletions

View File

@@ -0,0 +1,157 @@
package nodes
import (
"errors"
"log"
"net"
"os"
"os/exec"
"runtime"
"sync"
"time"
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
"github.com/TeaOSLab/EdgeHttpDNS/internal/utils"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/gosock/pkg/gosock"
)
type HTTPDNSNode struct {
sock *gosock.Sock
quitOnce sync.Once
quitCh chan struct{}
}
func NewHTTPDNSNode() *HTTPDNSNode {
return &HTTPDNSNode{
sock: gosock.NewTmpSock(teaconst.ProcessName),
quitCh: make(chan struct{}),
}
}
func (n *HTTPDNSNode) Run() {
err := n.listenSock()
if err != nil {
log.Println("[HTTPDNS_NODE]" + err.Error())
return
}
go n.start()
select {}
}
func (n *HTTPDNSNode) Daemon() {
path := os.TempDir() + "/" + teaconst.ProcessName + ".sock"
for {
conn, err := net.DialTimeout("unix", path, 1*time.Second)
if err != nil {
exe, exeErr := os.Executable()
if exeErr != nil {
log.Println("[DAEMON]", exeErr)
time.Sleep(1 * time.Second)
continue
}
cmd := exec.Command(exe)
cmd.Env = append(os.Environ(), "EdgeBackground=on")
if runtime.GOOS != "windows" {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
startErr := cmd.Start()
if startErr != nil {
log.Println("[DAEMON]", startErr)
time.Sleep(1 * time.Second)
continue
}
_ = cmd.Wait()
time.Sleep(5 * time.Second)
continue
}
_ = conn.Close()
time.Sleep(5 * time.Second)
}
}
func (n *HTTPDNSNode) InstallSystemService() error {
exe, err := os.Executable()
if err != nil {
return err
}
manager := utils.NewServiceManager(teaconst.SystemdServiceName, teaconst.ProductName)
return manager.Install(exe, []string{})
}
func (n *HTTPDNSNode) listenSock() error {
if runtime.GOOS == "windows" {
return nil
}
if n.sock.IsListening() {
reply, err := n.sock.Send(&gosock.Command{Code: "pid"})
if err == nil {
return errors.New("the process is already running, pid: " + maps.NewMap(reply.Params).GetString("pid"))
}
return errors.New("the process is already running")
}
go func() {
n.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()
n.stop()
time.Sleep(100 * time.Millisecond)
os.Exit(0)
}
})
err := n.sock.Listen()
if err != nil {
log.Println("[HTTPDNS_NODE][sock]", err.Error())
}
}()
return nil
}
func (n *HTTPDNSNode) start() {
log.Println("[HTTPDNS_NODE]started")
snapshotManager := NewSnapshotManager(n.quitCh)
statusManager := NewStatusManager(n.quitCh)
taskManager := NewTaskManager(n.quitCh, snapshotManager)
resolveServer := NewResolveServer(n.quitCh, snapshotManager)
go snapshotManager.Start()
go statusManager.Start()
go taskManager.Start()
go resolveServer.Start()
}
func (n *HTTPDNSNode) stop() {
n.quitOnce.Do(func() {
close(n.quitCh)
_ = n.sock.Close()
})
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
package nodes
import (
"log"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeHttpDNS/internal/rpc"
)
func reportRuntimeLog(level string, logType string, module string, description string, requestID string) {
rpcClient, err := rpc.SharedRPC()
if err != nil {
log.Println("[HTTPDNS_NODE][runtime-log]", err.Error())
return
}
nodeID := int64(0)
now := time.Now()
_, err = rpcClient.HTTPDNSRuntimeLogRPC.CreateHTTPDNSRuntimeLogs(rpcClient.Context(), &pb.CreateHTTPDNSRuntimeLogsRequest{
Logs: []*pb.HTTPDNSRuntimeLog{
{
NodeId: nodeID,
Level: level,
Type: logType,
Module: module,
Description: description,
Count: 1,
RequestId: requestID,
CreatedAt: now.Unix(),
Day: now.Format("20060102"),
},
},
})
if err != nil {
log.Println("[HTTPDNS_NODE][runtime-log]", err.Error())
}
}

View File

@@ -0,0 +1,160 @@
package nodes
import (
"fmt"
"log"
"strings"
"sync"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeHttpDNS/internal/rpc"
)
type LoadedDomain struct {
Domain *pb.HTTPDNSDomain
Rules []*pb.HTTPDNSCustomRule
}
type LoadedApp struct {
App *pb.HTTPDNSApp
Domains map[string]*LoadedDomain // key: lower(domain)
}
type LoadedSnapshot struct {
LoadedAt int64
NodeID int64
ClusterID int64
Clusters map[int64]*pb.HTTPDNSCluster
Apps map[string]*LoadedApp // key: lower(appId)
}
type SnapshotManager struct {
quitCh <-chan struct{}
ticker *time.Ticker
locker sync.RWMutex
snapshot *LoadedSnapshot
}
func NewSnapshotManager(quitCh <-chan struct{}) *SnapshotManager {
return &SnapshotManager{
quitCh: quitCh,
ticker: time.NewTicker(30 * time.Second),
}
}
func (m *SnapshotManager) Start() {
defer m.ticker.Stop()
if err := m.RefreshNow("startup"); err != nil {
log.Println("[HTTPDNS_NODE][snapshot]initial refresh failed:", err.Error())
}
for {
select {
case <-m.ticker.C:
if err := m.RefreshNow("periodic"); err != nil {
log.Println("[HTTPDNS_NODE][snapshot]periodic refresh failed:", err.Error())
}
case <-m.quitCh:
return
}
}
}
func (m *SnapshotManager) Current() *LoadedSnapshot {
m.locker.RLock()
defer m.locker.RUnlock()
return m.snapshot
}
func (m *SnapshotManager) RefreshNow(reason string) error {
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
nodeResp, err := rpcClient.HTTPDNSNodeRPC.FindHTTPDNSNode(rpcClient.Context(), &pb.FindHTTPDNSNodeRequest{
NodeId: 0,
})
if err != nil {
return err
}
if nodeResp.GetNode() == nil {
return fmt.Errorf("httpdns node info not found")
}
clusterResp, err := rpcClient.HTTPDNSClusterRPC.FindAllHTTPDNSClusters(rpcClient.Context(), &pb.FindAllHTTPDNSClustersRequest{})
if err != nil {
return err
}
clusters := map[int64]*pb.HTTPDNSCluster{}
for _, cluster := range clusterResp.GetClusters() {
if cluster == nil || cluster.GetId() <= 0 {
continue
}
clusters[cluster.GetId()] = cluster
}
appResp, err := rpcClient.HTTPDNSAppRPC.FindAllHTTPDNSApps(rpcClient.Context(), &pb.FindAllHTTPDNSAppsRequest{})
if err != nil {
return err
}
apps := map[string]*LoadedApp{}
for _, app := range appResp.GetApps() {
if app == nil || app.GetId() <= 0 || len(app.GetAppId()) == 0 {
continue
}
domainResp, err := rpcClient.HTTPDNSDomainRPC.ListHTTPDNSDomainsWithAppId(rpcClient.Context(), &pb.ListHTTPDNSDomainsWithAppIdRequest{
AppDbId: app.GetId(),
})
if err != nil {
log.Println("[HTTPDNS_NODE][snapshot]list domains failed, appId:", app.GetAppId(), "err:", err.Error())
continue
}
domains := map[string]*LoadedDomain{}
for _, domain := range domainResp.GetDomains() {
if domain == nil || domain.GetId() <= 0 || len(domain.GetDomain()) == 0 {
continue
}
ruleResp, err := rpcClient.HTTPDNSRuleRPC.ListHTTPDNSCustomRulesWithDomainId(rpcClient.Context(), &pb.ListHTTPDNSCustomRulesWithDomainIdRequest{
DomainId: domain.GetId(),
})
if err != nil {
log.Println("[HTTPDNS_NODE][snapshot]list rules failed, domain:", domain.GetDomain(), "err:", err.Error())
continue
}
domains[strings.ToLower(strings.TrimSpace(domain.GetDomain()))] = &LoadedDomain{
Domain: domain,
Rules: ruleResp.GetRules(),
}
}
apps[strings.ToLower(strings.TrimSpace(app.GetAppId()))] = &LoadedApp{
App: app,
Domains: domains,
}
}
snapshot := &LoadedSnapshot{
LoadedAt: time.Now().Unix(),
NodeID: nodeResp.GetNode().GetId(),
ClusterID: nodeResp.GetNode().GetClusterId(),
Clusters: clusters,
Apps: apps,
}
m.locker.Lock()
m.snapshot = snapshot
m.locker.Unlock()
reportRuntimeLog("info", "config", "snapshot", "snapshot refreshed: "+reason, fmt.Sprintf("snapshot-%d", time.Now().UnixNano()))
return nil
}

View File

@@ -0,0 +1,144 @@
package nodes
import (
"encoding/json"
"log"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeHttpDNS/internal/configs"
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
"github.com/TeaOSLab/EdgeHttpDNS/internal/rpc"
"github.com/TeaOSLab/EdgeHttpDNS/internal/utils"
)
type StatusManager struct {
quitCh <-chan struct{}
ticker *time.Ticker
}
func NewStatusManager(quitCh <-chan struct{}) *StatusManager {
return &StatusManager{
quitCh: quitCh,
ticker: time.NewTicker(30 * time.Second),
}
}
func (m *StatusManager) Start() {
defer m.ticker.Stop()
m.update()
for {
select {
case <-m.ticker.C:
m.update()
case <-m.quitCh:
return
}
}
}
func (m *StatusManager) update() {
status := m.collectStatus()
statusJSON, err := json.Marshal(status)
if err != nil {
log.Println("[HTTPDNS_NODE][status]marshal status failed:", err.Error())
return
}
rpcClient, err := rpc.SharedRPC()
if err != nil {
log.Println("[HTTPDNS_NODE][status]rpc unavailable:", err.Error())
return
}
config, err := configs.SharedAPIConfig()
if err != nil {
log.Println("[HTTPDNS_NODE][status]load config failed:", err.Error())
return
}
nodeId, _ := strconv.ParseInt(config.NodeId, 10, 64)
_, err = rpcClient.HTTPDNSNodeRPC.UpdateHTTPDNSNodeStatus(rpcClient.Context(), &pb.UpdateHTTPDNSNodeStatusRequest{
NodeId: nodeId,
IsUp: true,
IsInstalled: true,
IsActive: true,
StatusJSON: statusJSON,
})
if err != nil {
log.Println("[HTTPDNS_NODE][status]update status failed:", err.Error())
}
}
func (m *StatusManager) collectStatus() *nodeconfigs.NodeStatus {
now := time.Now().Unix()
status := &nodeconfigs.NodeStatus{
BuildVersion: teaconst.Version,
BuildVersionCode: utils.VersionToLong(teaconst.Version),
ConfigVersion: 0,
OS: runtime.GOOS,
Arch: runtime.GOARCH,
CPULogicalCount: runtime.NumCPU(),
CPUPhysicalCount: runtime.NumCPU(),
IsActive: true,
ConnectionCount: 0,
UpdatedAt: now,
Timestamp: now,
}
rpcClient, err := rpc.SharedRPC()
if err == nil {
total, failed, avgCostSeconds := rpcClient.GetAndResetMetrics()
if total > 0 {
status.APISuccessPercent = float64(total-failed) * 100.0 / float64(total)
status.APIAvgCostSeconds = avgCostSeconds
}
}
hostname, _ := os.Hostname()
status.Hostname = hostname
exePath, _ := os.Executable()
status.ExePath = exePath
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
status.MemoryTotal = memStats.Sys
if status.MemoryTotal > 0 {
status.MemoryUsage = float64(memStats.Alloc) / float64(status.MemoryTotal)
}
load1m, load5m, load15m := readLoadAvg()
status.Load1m = load1m
status.Load5m = load5m
status.Load15m = load15m
return status
}
func readLoadAvg() (float64, float64, float64) {
data, err := os.ReadFile("/proc/loadavg")
if err != nil {
return 0, 0, 0
}
parts := strings.Fields(strings.TrimSpace(string(data)))
if len(parts) < 3 {
return 0, 0, 0
}
load1m, _ := strconv.ParseFloat(parts[0], 64)
load5m, _ := strconv.ParseFloat(parts[1], 64)
load15m, _ := strconv.ParseFloat(parts[2], 64)
return load1m, load5m, load15m
}

View File

@@ -0,0 +1,131 @@
package nodes
import (
"fmt"
"log"
"time"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
"github.com/TeaOSLab/EdgeHttpDNS/internal/rpc"
)
type TaskManager struct {
quitCh <-chan struct{}
ticker *time.Ticker
version int64
snapshotManager *SnapshotManager
}
func NewTaskManager(quitCh <-chan struct{}, snapshotManager *SnapshotManager) *TaskManager {
return &TaskManager{
quitCh: quitCh,
ticker: time.NewTicker(20 * time.Second),
version: 0,
snapshotManager: snapshotManager,
}
}
func (m *TaskManager) Start() {
defer m.ticker.Stop()
m.processTasks()
for {
select {
case <-m.ticker.C:
m.processTasks()
case <-m.quitCh:
return
}
}
}
func (m *TaskManager) processTasks() {
rpcClient, err := rpc.SharedRPC()
if err != nil {
log.Println("[HTTPDNS_NODE][task]rpc unavailable:", err.Error())
return
}
resp, err := rpcClient.NodeTaskRPC.FindNodeTasks(rpcClient.Context(), &pb.FindNodeTasksRequest{
Version: m.version,
})
if err != nil {
log.Println("[HTTPDNS_NODE][task]fetch tasks failed:", err.Error())
return
}
for _, task := range resp.GetNodeTasks() {
ok, errorMessage := m.handleTask(task)
_, reportErr := rpcClient.NodeTaskRPC.ReportNodeTaskDone(rpcClient.Context(), &pb.ReportNodeTaskDoneRequest{
NodeTaskId: task.GetId(),
IsOk: ok,
Error: errorMessage,
})
if reportErr != nil {
log.Println("[HTTPDNS_NODE][task]report task result failed:", reportErr.Error())
}
if task.GetVersion() > m.version {
m.version = task.GetVersion()
}
}
}
func (m *TaskManager) handleTask(task *pb.NodeTask) (bool, string) {
taskType := task.GetType()
requestID := fmt.Sprintf("task-%d", task.GetId())
switch taskType {
case teaconst.TaskTypeHTTPDNSConfigChanged:
if m.snapshotManager != nil {
if err := m.snapshotManager.RefreshNow(taskType); err != nil {
reportRuntimeLog("error", "config", "task-manager", "refresh snapshot failed: "+err.Error(), requestID)
return false, err.Error()
}
}
reportRuntimeLog("info", "config", "task-manager", "HTTPDNS configuration updated", requestID)
return true, ""
case teaconst.TaskTypeHTTPDNSAppChanged:
if m.snapshotManager != nil {
if err := m.snapshotManager.RefreshNow(taskType); err != nil {
reportRuntimeLog("error", "app", "task-manager", "refresh snapshot failed: "+err.Error(), requestID)
return false, err.Error()
}
}
reportRuntimeLog("info", "app", "task-manager", "HTTPDNS app policy updated", requestID)
return true, ""
case teaconst.TaskTypeHTTPDNSDomainChanged:
if m.snapshotManager != nil {
if err := m.snapshotManager.RefreshNow(taskType); err != nil {
reportRuntimeLog("error", "domain", "task-manager", "refresh snapshot failed: "+err.Error(), requestID)
return false, err.Error()
}
}
reportRuntimeLog("info", "domain", "task-manager", "HTTPDNS domain binding updated", requestID)
return true, ""
case teaconst.TaskTypeHTTPDNSRuleChanged:
if m.snapshotManager != nil {
if err := m.snapshotManager.RefreshNow(taskType); err != nil {
reportRuntimeLog("error", "rule", "task-manager", "refresh snapshot failed: "+err.Error(), requestID)
return false, err.Error()
}
}
reportRuntimeLog("info", "rule", "task-manager", "HTTPDNS custom rule updated", requestID)
return true, ""
case teaconst.TaskTypeHTTPDNSTLSChanged:
if m.snapshotManager != nil {
if err := m.snapshotManager.RefreshNow(taskType); err != nil {
reportRuntimeLog("error", "tls", "task-manager", "refresh snapshot failed: "+err.Error(), requestID)
return false, err.Error()
}
}
reportRuntimeLog("info", "tls", "task-manager", "HTTPDNS TLS config updated", requestID)
return true, ""
default:
reportRuntimeLog("warning", "task", "task-manager", "unknown task type: "+taskType, requestID)
return true, ""
}
}