浏览代码

feat: use settings file to predefine nodes #169

Jacky 1 年之前
父节点
当前提交
a689608bdb
共有 7 个文件被更改,包括 192 次插入49 次删除
  1. 27 24
      app.example.ini
  2. 1 0
      internal/kernal/boot.go
  3. 71 0
      internal/kernal/cluster.go
  4. 47 0
      internal/kernal/cluster_test.go
  5. 4 0
      settings/cluster.go
  6. 15 0
      settings/cluster_test.go
  7. 27 25
      settings/settings.go

+ 27 - 24
app.example.ini

@@ -1,45 +1,46 @@
+; suppress inspection "DuplicateKeyInSection" for whole file
 [server]
 HttpPort             = 9000
 RunMode              = debug
-JwtSecret            =
-Email                =
+JwtSecret            = 
+Email                = 
 HTTPChallengePort    = 9180
 StartCmd             = bash
 Database             = database
-CADir                =
-GithubProxy          =
-NodeSecret           =
+CADir                = 
+GithubProxy          = 
+NodeSecret           = 
 Demo                 = false
 PageSize             = 10
 HttpHost             = 0.0.0.0
 CertRenewalInterval  = 7
-RecursiveNameservers =
+RecursiveNameservers = 
 SkipInstallation     = false
-Name                 =
+Name                 = 
 
 [nginx]
 AccessLogPath = /var/log/nginx/access.log
 ErrorLogPath  = /var/log/nginx/error.log
-ConfigDir     =
-PIDPath       =
-TestConfigCmd =
-ReloadCmd     =
-RestartCmd    =
+ConfigDir     = 
+PIDPath       = 
+TestConfigCmd = 
+ReloadCmd     = 
+RestartCmd    = 
 
 [openai]
-Model   =
-BaseUrl =
-Proxy   =
-Token   =
+Model   = 
+BaseUrl = 
+Proxy   = 
+Token   = 
 
 [casdoor]
-Endpoint     =
-ClientId     =
-ClientSecret =
-Certificate  =
-Organization =
-Application  =
-RedirectUri  =
+Endpoint     = 
+ClientId     = 
+ClientSecret = 
+Certificate  = 
+Organization = 
+Application  = 
+RedirectUri  = 
 
 [logrotate]
 Enabled  = false
@@ -47,4 +48,6 @@ CMD      = logrotate /etc/logrotate.d/nginx
 Interval = 1440
 
 [cluster]
-Node =
+Node = http://10.0.0.1:9000?name=node1&node_secret=my-node-secret&enabled=true
+Node = http://10.0.0.2:9000?name=node2&node_secret=my-node-secret&enabled=true
+Node = http://10.0.0.3?name=node3&node_secret=my-node-secret&enabled=true

+ 1 - 0
internal/kernal/boot.go

@@ -44,6 +44,7 @@ func InitAfterDatabase() {
 		registerPredefinedUser,
 		cert.InitRegister,
 		InitCronJobs,
+		registerPredefinedClusterNodes,
 		analytic.RetrieveNodesStatus,
 	}
 

+ 71 - 0
internal/kernal/cluster.go

@@ -0,0 +1,71 @@
+package kernal
+
+import (
+	"github.com/0xJacky/Nginx-UI/internal/logger"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/query"
+	"github.com/0xJacky/Nginx-UI/settings"
+	"gorm.io/gen/field"
+	"net/url"
+	"strings"
+)
+
+func registerPredefinedClusterNodes() {
+	if len(settings.ClusterSettings.Node) == 0 {
+		return
+	}
+
+	q := query.Environment
+	for _, nodeUrl := range settings.ClusterSettings.Node {
+		func() {
+			node, err := parseNodeUrl(nodeUrl)
+			if err != nil {
+				logger.Error(nodeUrl, err)
+				return
+			}
+
+			if node.Name == "" {
+				logger.Error(nodeUrl, "Node name is required")
+				return
+			}
+
+			if node.URL == "" {
+				logger.Error(nodeUrl, "Node URL is required")
+				return
+			}
+
+			if node.Token == "" {
+				logger.Error(nodeUrl, "Node Token is required")
+				return
+			}
+
+			_, err = q.Where(q.URL.Eq(node.URL)).
+				Attrs(field.Attrs(node)).
+				FirstOrCreate()
+			if err != nil {
+				logger.Error(node.URL, err)
+			}
+		}()
+	}
+}
+
+func parseNodeUrl(nodeUrl string) (env *model.Environment, err error) {
+	u, err := url.Parse(nodeUrl)
+	if err != nil {
+		return
+	}
+	var sb strings.Builder
+	sb.WriteString(u.Scheme)
+	sb.WriteString("://")
+	sb.WriteString(u.Host)
+	sb.WriteString(u.Path)
+
+	env = &model.Environment{
+		Name:    u.Query().Get("name"),
+		URL:     sb.String(),
+		Token:   u.Query().Get("node_secret"),
+		Enabled: u.Query().Get("enabled") == "true",
+	}
+
+	return
+}

+ 47 - 0
internal/kernal/cluster_test.go

@@ -0,0 +1,47 @@
+package kernal
+
+import (
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func Test_parseNodeUrl(t *testing.T) {
+	settings.Init("../../app.example.ini")
+	t.Log(settings.ClusterSettings.Node)
+	node := settings.ClusterSettings.Node[0]
+
+	env, err := parseNodeUrl(node)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assert.Equal(t, "node1", env.Name)
+	assert.Equal(t, "http://10.0.0.1:9000", env.URL)
+	assert.Equal(t, "my-node-secret", env.Token)
+	assert.Equal(t, true, env.Enabled)
+
+	node = settings.ClusterSettings.Node[1]
+
+	env, err = parseNodeUrl(node)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assert.Equal(t, "node2", env.Name)
+	assert.Equal(t, "http://10.0.0.2:9000", env.URL)
+	assert.Equal(t, "my-node-secret", env.Token)
+	assert.Equal(t, true, env.Enabled)
+
+	node = settings.ClusterSettings.Node[2]
+
+	env, err = parseNodeUrl(node)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	assert.Equal(t, "node3", env.Name)
+	assert.Equal(t, "http://10.0.0.3", env.URL)
+	assert.Equal(t, "my-node-secret", env.Token)
+	assert.Equal(t, true, env.Enabled)
+}

+ 4 - 0
settings/cluster.go

@@ -3,3 +3,7 @@ package settings
 type Cluster struct {
 	Node []string `ini:",,allowshadow"`
 }
+
+var ClusterSettings = Cluster{
+	Node: []string{},
+}

+ 15 - 0
settings/cluster_test.go

@@ -0,0 +1,15 @@
+package settings
+
+import (
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestCluster(t *testing.T) {
+	Init("../app.example.ini")
+
+	assert.Equal(t, []string{
+		"http://10.0.0.1:9000?name=node1&node_secret=my-node-secret&enabled=true",
+		"http://10.0.0.2:9000?name=node2&node_secret=my-node-secret&enabled=true",
+	}, ClusterSettings.Node)
+}

+ 27 - 25
settings/settings.go

@@ -6,8 +6,8 @@ import (
 	"gopkg.in/ini.v1"
 	"log"
 	"os"
-    "reflect"
-    "strings"
+	"reflect"
+	"strings"
 	"time"
 )
 
@@ -26,6 +26,7 @@ var sections = map[string]interface{}{
 	"openai":    &OpenAISettings,
 	"casdoor":   &CasdoorSettings,
 	"logrotate": &LogrotateSettings,
+	"cluster":   &ClusterSettings,
 }
 
 func init() {
@@ -40,10 +41,15 @@ func Init(confPath string) {
 
 func Setup() {
 	var err error
-	Conf, err = ini.LooseLoad(ConfPath)
+	Conf, err = ini.LoadSources(ini.LoadOptions{
+		Loose:        true,
+		AllowShadows: true,
+	}, ConfPath)
+
 	if err != nil {
 		log.Fatalf("settings.Setup: %v\n", err)
 	}
+
 	MapTo()
 
 	parseEnv(&ServerSettings, "SERVER_")
@@ -70,8 +76,6 @@ func MapTo() {
 	}
 }
 
-
-
 func Save() (err error) {
 	for k, v := range sections {
 		reflectFrom(k, v)
@@ -85,30 +89,30 @@ func Save() (err error) {
 }
 
 func ProtectedFill(targetSettings interface{}, newSettings interface{}) {
-    s := reflect.TypeOf(targetSettings).Elem()
-    vt := reflect.ValueOf(targetSettings).Elem()
-    vn := reflect.ValueOf(newSettings).Elem()
-
-    // copy the values from new to target settings if it is not protected
-    for i := 0; i < s.NumField(); i++ {
-        if s.Field(i).Tag.Get("protected") != "true" {
-            vt.Field(i).Set(vn.Field(i))
-        }
-    }
+	s := reflect.TypeOf(targetSettings).Elem()
+	vt := reflect.ValueOf(targetSettings).Elem()
+	vn := reflect.ValueOf(newSettings).Elem()
+
+	// copy the values from new to target settings if it is not protected
+	for i := 0; i < s.NumField(); i++ {
+		if s.Field(i).Tag.Get("protected") != "true" {
+			vt.Field(i).Set(vn.Field(i))
+		}
+	}
 }
 
 func mapTo(section string, v interface{}) {
-    err := Conf.Section(section).MapTo(v)
-    if err != nil {
-        log.Fatalf("Cfg.MapTo %s err: %v", section, err)
-    }
+	err := Conf.Section(section).MapTo(v)
+	if err != nil {
+		log.Fatalf("Cfg.MapTo %s err: %v", section, err)
+	}
 }
 
 func reflectFrom(section string, v interface{}) {
-    err := Conf.Section(section).ReflectFrom(v)
-    if err != nil {
-        log.Fatalf("Cfg.ReflectFrom %s err: %v", section, err)
-    }
+	err := Conf.Section(section).ReflectFrom(v)
+	if err != nil {
+		log.Fatalf("Cfg.ReflectFrom %s err: %v", section, err)
+	}
 }
 
 func parseEnv(ptr interface{}, prefix string) {
@@ -121,5 +125,3 @@ func parseEnv(ptr interface{}, prefix string) {
 		log.Fatalf("settings.parseEnv: %v\n", err)
 	}
 }
-
-