管理端全部功能跑通
This commit is contained in:
149
EdgeHttpDNS/internal/apps/app_cmd.go
Normal file
149
EdgeHttpDNS/internal/apps/app_cmd.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
)
|
||||
|
||||
type AppCmd struct {
|
||||
product string
|
||||
version string
|
||||
usage string
|
||||
directives map[string]func()
|
||||
sock *gosock.Sock
|
||||
}
|
||||
|
||||
func NewAppCmd() *AppCmd {
|
||||
return &AppCmd{
|
||||
directives: map[string]func(){},
|
||||
sock: gosock.NewTmpSock(teaconst.ProcessName),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppCmd) Product(product string) *AppCmd {
|
||||
a.product = product
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AppCmd) Version(version string) *AppCmd {
|
||||
a.version = version
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AppCmd) Usage(usage string) *AppCmd {
|
||||
a.usage = usage
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *AppCmd) On(arg string, callback func()) {
|
||||
a.directives[arg] = callback
|
||||
}
|
||||
|
||||
func (a *AppCmd) Run(main func()) {
|
||||
args := os.Args[1:]
|
||||
if len(args) == 0 {
|
||||
main()
|
||||
return
|
||||
}
|
||||
|
||||
switch args[0] {
|
||||
case "-v", "version", "-version", "--version":
|
||||
fmt.Println(a.product+" v"+a.version, "(build:", runtimeString()+")")
|
||||
return
|
||||
case "help", "-h", "--help":
|
||||
fmt.Println(a.product + " v" + a.version)
|
||||
fmt.Println("Usage:")
|
||||
fmt.Println(" " + a.usage)
|
||||
return
|
||||
case "start":
|
||||
a.runDirective("start:before")
|
||||
a.runStart()
|
||||
return
|
||||
case "stop":
|
||||
a.runStop()
|
||||
return
|
||||
case "restart":
|
||||
a.runStop()
|
||||
time.Sleep(1 * time.Second)
|
||||
a.runDirective("start:before")
|
||||
a.runStart()
|
||||
return
|
||||
case "status":
|
||||
a.runStatus()
|
||||
return
|
||||
default:
|
||||
if callback, ok := a.directives[args[0]]; ok {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppCmd) runStart() {
|
||||
pid := a.getPID()
|
||||
if pid > 0 {
|
||||
fmt.Println(a.product+" already started, pid:", pid)
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
cmd.Env = append(os.Environ(), "EdgeBackground=on")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
fmt.Println(a.product+" start failed:", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(a.product+" started, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
func (a *AppCmd) runStop() {
|
||||
pid := a.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(a.product + " not started")
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = a.sock.Send(&gosock.Command{Code: "stop"})
|
||||
fmt.Println(a.product+" stopped, pid:", pid)
|
||||
}
|
||||
|
||||
func (a *AppCmd) runStatus() {
|
||||
pid := a.getPID()
|
||||
if pid == 0 {
|
||||
fmt.Println(a.product + " not started")
|
||||
return
|
||||
}
|
||||
fmt.Println(a.product+" is running, pid:", pid)
|
||||
}
|
||||
|
||||
func (a *AppCmd) runDirective(name string) {
|
||||
if callback, ok := a.directives[name]; ok && callback != nil {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *AppCmd) getPID() int {
|
||||
if !a.sock.IsListening() {
|
||||
return 0
|
||||
}
|
||||
|
||||
reply, err := a.sock.Send(&gosock.Command{Code: "pid"})
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return maps.NewMap(reply.Params).GetInt("pid")
|
||||
}
|
||||
|
||||
func runtimeString() string {
|
||||
return runtime.GOOS + "/" + runtime.GOARCH
|
||||
}
|
||||
23
EdgeHttpDNS/internal/const/const.go
Normal file
23
EdgeHttpDNS/internal/const/const.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.8"
|
||||
|
||||
ProductName = "Edge HTTPDNS"
|
||||
ProcessName = "edge-httpdns"
|
||||
ProductNameZH = "Edge HTTPDNS"
|
||||
|
||||
Role = "httpdns"
|
||||
|
||||
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
SystemdServiceName = "edge-httpdns"
|
||||
|
||||
// HTTPDNS node tasks from API.
|
||||
TaskTypeHTTPDNSConfigChanged = "httpdnsConfigChanged"
|
||||
TaskTypeHTTPDNSAppChanged = "httpdnsAppChanged"
|
||||
TaskTypeHTTPDNSDomainChanged = "httpdnsDomainChanged"
|
||||
TaskTypeHTTPDNSRuleChanged = "httpdnsRuleChanged"
|
||||
TaskTypeHTTPDNSTLSChanged = "httpdnsTLSChanged"
|
||||
)
|
||||
7
EdgeHttpDNS/internal/encrypt/method.go
Normal file
7
EdgeHttpDNS/internal/encrypt/method.go
Normal file
@@ -0,0 +1,7 @@
|
||||
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)
|
||||
}
|
||||
64
EdgeHttpDNS/internal/encrypt/method_aes_256_cfb.go
Normal file
64
EdgeHttpDNS/internal/encrypt/method_aes_256_cfb.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
type AES256CFBMethod struct {
|
||||
block cipher.Block
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (m *AES256CFBMethod) Init(key, iv []byte) error {
|
||||
keyLen := len(key)
|
||||
if keyLen > 32 {
|
||||
key = key[:32]
|
||||
} else if keyLen < 32 {
|
||||
key = append(key, bytes.Repeat([]byte{' '}, 32-keyLen)...)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.block = block
|
||||
|
||||
ivLen := len(iv)
|
||||
if ivLen > aes.BlockSize {
|
||||
iv = iv[:aes.BlockSize]
|
||||
} else if ivLen < aes.BlockSize {
|
||||
iv = append(iv, bytes.Repeat([]byte{' '}, aes.BlockSize-ivLen)...)
|
||||
}
|
||||
m.iv = iv
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AES256CFBMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
dst = make([]byte, len(src))
|
||||
cipher.NewCFBEncrypter(m.block, m.iv).XORKeyStream(dst, src)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *AES256CFBMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
if len(dst) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = RecoverMethodPanic(recover())
|
||||
}()
|
||||
|
||||
src = make([]byte, len(dst))
|
||||
cipher.NewCFBDecrypter(m.block, m.iv).XORKeyStream(src, dst)
|
||||
return
|
||||
}
|
||||
15
EdgeHttpDNS/internal/encrypt/method_raw.go
Normal file
15
EdgeHttpDNS/internal/encrypt/method_raw.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package encrypt
|
||||
|
||||
type RawMethod struct{}
|
||||
|
||||
func (m *RawMethod) Init(key []byte, iv []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *RawMethod) Encrypt(src []byte) (dst []byte, err error) {
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func (m *RawMethod) Decrypt(dst []byte) (src []byte, err error) {
|
||||
return dst, nil
|
||||
}
|
||||
40
EdgeHttpDNS/internal/encrypt/method_utils.go
Normal file
40
EdgeHttpDNS/internal/encrypt/method_utils.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package encrypt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var methods = map[string]reflect.Type{
|
||||
"raw": reflect.TypeOf(new(RawMethod)).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 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s, ok := err.(string); ok {
|
||||
return errors.New(s)
|
||||
}
|
||||
if e, ok := err.(error); ok {
|
||||
return e
|
||||
}
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
157
EdgeHttpDNS/internal/nodes/httpdns_node.go
Normal file
157
EdgeHttpDNS/internal/nodes/httpdns_node.go
Normal 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()
|
||||
})
|
||||
}
|
||||
1119
EdgeHttpDNS/internal/nodes/resolve_server.go
Normal file
1119
EdgeHttpDNS/internal/nodes/resolve_server.go
Normal file
File diff suppressed because it is too large
Load Diff
39
EdgeHttpDNS/internal/nodes/runtime_log.go
Normal file
39
EdgeHttpDNS/internal/nodes/runtime_log.go
Normal 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())
|
||||
}
|
||||
}
|
||||
160
EdgeHttpDNS/internal/nodes/snapshot_manager.go
Normal file
160
EdgeHttpDNS/internal/nodes/snapshot_manager.go
Normal 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
|
||||
}
|
||||
144
EdgeHttpDNS/internal/nodes/status_manager.go
Normal file
144
EdgeHttpDNS/internal/nodes/status_manager.go
Normal 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
|
||||
}
|
||||
131
EdgeHttpDNS/internal/nodes/task_manager.go
Normal file
131
EdgeHttpDNS/internal/nodes/task_manager.go
Normal 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, ""
|
||||
}
|
||||
}
|
||||
222
EdgeHttpDNS/internal/rpc/rpc_client.go
Normal file
222
EdgeHttpDNS/internal/rpc/rpc_client.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/encrypt"
|
||||
"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"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
||||
type RPCClient struct {
|
||||
apiConfig *configs.APIConfig
|
||||
conns []*grpc.ClientConn
|
||||
locker sync.RWMutex
|
||||
|
||||
NodeTaskRPC pb.NodeTaskServiceClient
|
||||
HTTPDNSNodeRPC pb.HTTPDNSNodeServiceClient
|
||||
HTTPDNSClusterRPC pb.HTTPDNSClusterServiceClient
|
||||
HTTPDNSAppRPC pb.HTTPDNSAppServiceClient
|
||||
HTTPDNSDomainRPC pb.HTTPDNSDomainServiceClient
|
||||
HTTPDNSRuleRPC pb.HTTPDNSRuleServiceClient
|
||||
HTTPDNSRuntimeLogRPC pb.HTTPDNSRuntimeLogServiceClient
|
||||
HTTPDNSAccessLogRPC pb.HTTPDNSAccessLogServiceClient
|
||||
HTTPDNSSandboxRPC pb.HTTPDNSSandboxServiceClient
|
||||
|
||||
totalRequests int64
|
||||
failedRequests int64
|
||||
totalCostMs int64
|
||||
}
|
||||
|
||||
|
||||
func NewRPCClient(apiConfig *configs.APIConfig) (*RPCClient, error) {
|
||||
if apiConfig == nil {
|
||||
return nil, errors.New("api config should not be nil")
|
||||
}
|
||||
|
||||
client := &RPCClient{apiConfig: apiConfig}
|
||||
client.NodeTaskRPC = pb.NewNodeTaskServiceClient(client)
|
||||
client.HTTPDNSNodeRPC = pb.NewHTTPDNSNodeServiceClient(client)
|
||||
client.HTTPDNSClusterRPC = pb.NewHTTPDNSClusterServiceClient(client)
|
||||
client.HTTPDNSAppRPC = pb.NewHTTPDNSAppServiceClient(client)
|
||||
client.HTTPDNSDomainRPC = pb.NewHTTPDNSDomainServiceClient(client)
|
||||
client.HTTPDNSRuleRPC = pb.NewHTTPDNSRuleServiceClient(client)
|
||||
client.HTTPDNSRuntimeLogRPC = pb.NewHTTPDNSRuntimeLogServiceClient(client)
|
||||
client.HTTPDNSAccessLogRPC = pb.NewHTTPDNSAccessLogServiceClient(client)
|
||||
client.HTTPDNSSandboxRPC = pb.NewHTTPDNSSandboxServiceClient(client)
|
||||
|
||||
err := client.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) Context() context.Context {
|
||||
ctx := context.Background()
|
||||
payload := maps.Map{
|
||||
"timestamp": time.Now().Unix(),
|
||||
"type": "httpdns",
|
||||
"userId": 0,
|
||||
}
|
||||
|
||||
method, err := encrypt.NewMethodInstance(teaconst.EncryptMethod, c.apiConfig.Secret, c.apiConfig.NodeId)
|
||||
if err != nil {
|
||||
return context.Background()
|
||||
}
|
||||
encrypted, err := method.Encrypt(payload.AsJSON())
|
||||
if err != nil {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
token := base64.StdEncoding.EncodeToString(encrypted)
|
||||
return metadata.AppendToOutgoingContext(ctx, "nodeId", c.apiConfig.NodeId, "token", token)
|
||||
}
|
||||
|
||||
func (c *RPCClient) UpdateConfig(config *configs.APIConfig) error {
|
||||
c.apiConfig = config
|
||||
c.locker.Lock()
|
||||
defer c.locker.Unlock()
|
||||
return c.init()
|
||||
}
|
||||
|
||||
func (c *RPCClient) init() error {
|
||||
conns := []*grpc.ClientConn{}
|
||||
for _, endpoint := range c.apiConfig.RPCEndpoints {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse endpoint failed: %w", err)
|
||||
}
|
||||
|
||||
var conn *grpc.ClientConn
|
||||
callOptions := grpc.WithDefaultCallOptions(
|
||||
grpc.MaxCallRecvMsgSize(128<<20),
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
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("invalid endpoint scheme '" + u.Scheme + "'")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
|
||||
if len(conns) == 0 {
|
||||
return errors.New("no available rpc endpoints")
|
||||
}
|
||||
|
||||
c.conns = conns
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RPCClient) pickConn() *grpc.ClientConn {
|
||||
c.locker.RLock()
|
||||
defer c.locker.RUnlock()
|
||||
|
||||
countConns := len(c.conns)
|
||||
if countConns == 0 {
|
||||
return nil
|
||||
}
|
||||
if countConns == 1 {
|
||||
return c.conns[0]
|
||||
}
|
||||
|
||||
for _, state := range []connectivity.State{
|
||||
connectivity.Ready,
|
||||
connectivity.Idle,
|
||||
connectivity.Connecting,
|
||||
connectivity.TransientFailure,
|
||||
} {
|
||||
available := []*grpc.ClientConn{}
|
||||
for _, conn := range c.conns {
|
||||
if conn.GetState() == state {
|
||||
available = append(available, conn)
|
||||
}
|
||||
}
|
||||
if len(available) > 0 {
|
||||
return c.randConn(available)
|
||||
}
|
||||
}
|
||||
|
||||
return c.randConn(c.conns)
|
||||
}
|
||||
|
||||
func (c *RPCClient) randConn(conns []*grpc.ClientConn) *grpc.ClientConn {
|
||||
l := len(conns)
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
if l == 1 {
|
||||
return conns[0]
|
||||
}
|
||||
return conns[rands.Int(0, l-1)]
|
||||
}
|
||||
|
||||
func (c *RPCClient) Invoke(ctx context.Context, method string, args interface{}, reply interface{}, opts ...grpc.CallOption) error {
|
||||
conn := c.pickConn()
|
||||
if conn == nil {
|
||||
return errors.New("can not get available grpc connection")
|
||||
}
|
||||
|
||||
atomic.AddInt64(&c.totalRequests, 1)
|
||||
start := time.Now()
|
||||
err := conn.Invoke(ctx, method, args, reply, opts...)
|
||||
costMs := time.Since(start).Milliseconds()
|
||||
atomic.AddInt64(&c.totalCostMs, costMs)
|
||||
|
||||
if err != nil {
|
||||
atomic.AddInt64(&c.failedRequests, 1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *RPCClient) GetAndResetMetrics() (total int64, failed int64, avgCostSeconds float64) {
|
||||
total = atomic.SwapInt64(&c.totalRequests, 0)
|
||||
failed = atomic.SwapInt64(&c.failedRequests, 0)
|
||||
costMs := atomic.SwapInt64(&c.totalCostMs, 0)
|
||||
|
||||
if total > 0 {
|
||||
avgCostSeconds = float64(costMs) / float64(total) / 1000.0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
func (c *RPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
conn := c.pickConn()
|
||||
if conn == nil {
|
||||
return nil, errors.New("can not get available grpc connection")
|
||||
}
|
||||
return conn.NewStream(ctx, desc, method, opts...)
|
||||
}
|
||||
31
EdgeHttpDNS/internal/rpc/shared.go
Normal file
31
EdgeHttpDNS/internal/rpc/shared.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/TeaOSLab/EdgeHttpDNS/internal/configs"
|
||||
)
|
||||
|
||||
var sharedRPCClient *RPCClient
|
||||
var sharedLocker sync.Mutex
|
||||
|
||||
func SharedRPC() (*RPCClient, error) {
|
||||
sharedLocker.Lock()
|
||||
defer sharedLocker.Unlock()
|
||||
|
||||
config, err := configs.SharedAPIConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sharedRPCClient == nil {
|
||||
client, err := NewRPCClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sharedRPCClient = client
|
||||
return sharedRPCClient, nil
|
||||
}
|
||||
|
||||
return sharedRPCClient, nil
|
||||
}
|
||||
18
EdgeHttpDNS/internal/utils/ip.go
Normal file
18
EdgeHttpDNS/internal/utils/ip.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
46
EdgeHttpDNS/internal/utils/service.go
Normal file
46
EdgeHttpDNS/internal/utils/service.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
)
|
||||
|
||||
type ServiceManager struct {
|
||||
Name string
|
||||
Description string
|
||||
|
||||
onceLocker sync.Once
|
||||
}
|
||||
|
||||
func NewServiceManager(name, description string) *ServiceManager {
|
||||
manager := &ServiceManager{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
manager.resetRoot()
|
||||
return manager
|
||||
}
|
||||
|
||||
func (m *ServiceManager) setup() {
|
||||
m.onceLocker.Do(func() {})
|
||||
}
|
||||
|
||||
func (m *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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
65
EdgeHttpDNS/internal/utils/service_linux.go
Normal file
65
EdgeHttpDNS/internal/utils/service_linux.go
Normal file
@@ -0,0 +1,65 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
teaconst "github.com/TeaOSLab/EdgeHttpDNS/internal/const"
|
||||
)
|
||||
|
||||
var systemdServiceFile = "/etc/systemd/system/" + teaconst.SystemdServiceName + ".service"
|
||||
|
||||
func (m *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 err
|
||||
}
|
||||
|
||||
desc := `[Unit]
|
||||
Description=GoEdge HTTPDNS Node Service
|
||||
Before=shutdown.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` restart
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target`
|
||||
|
||||
err = os.WriteFile(systemdServiceFile, []byte(desc), 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = exec.Command(systemd, "stop", teaconst.SystemdServiceName+".service").Run()
|
||||
_ = exec.Command(systemd, "daemon-reload").Run()
|
||||
return exec.Command(systemd, "enable", teaconst.SystemdServiceName+".service").Run()
|
||||
}
|
||||
|
||||
func (m *ServiceManager) Uninstall() error {
|
||||
if os.Getgid() != 0 {
|
||||
return errors.New("only root users can uninstall the service")
|
||||
}
|
||||
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = exec.Command(systemd, "disable", teaconst.SystemdServiceName+".service").Run()
|
||||
_ = exec.Command(systemd, "daemon-reload").Run()
|
||||
return os.Remove(systemdServiceFile)
|
||||
}
|
||||
14
EdgeHttpDNS/internal/utils/service_others.go
Normal file
14
EdgeHttpDNS/internal/utils/service_others.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package utils
|
||||
|
||||
import "errors"
|
||||
|
||||
func (m *ServiceManager) Install(exePath string, args []string) error {
|
||||
return errors.New("service install is only supported on linux in this version")
|
||||
}
|
||||
|
||||
func (m *ServiceManager) Uninstall() error {
|
||||
return errors.New("service uninstall is only supported on linux in this version")
|
||||
}
|
||||
15
EdgeHttpDNS/internal/utils/version.go
Normal file
15
EdgeHttpDNS/internal/utils/version.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
|
||||
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