diff --git a/app.example.ini b/app.example.ini index 2a609d2a..ca687a99 100644 --- a/app.example.ini +++ b/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 diff --git a/internal/kernal/boot.go b/internal/kernal/boot.go index ec2baa41..7b21e6cf 100644 --- a/internal/kernal/boot.go +++ b/internal/kernal/boot.go @@ -44,6 +44,7 @@ func InitAfterDatabase() { registerPredefinedUser, cert.InitRegister, InitCronJobs, + registerPredefinedClusterNodes, analytic.RetrieveNodesStatus, } diff --git a/internal/kernal/cluster.go b/internal/kernal/cluster.go new file mode 100644 index 00000000..ae986810 --- /dev/null +++ b/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 +} diff --git a/internal/kernal/cluster_test.go b/internal/kernal/cluster_test.go new file mode 100644 index 00000000..6373dac2 --- /dev/null +++ b/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) +} diff --git a/settings/cluster.go b/settings/cluster.go index 21c9a127..1d3aa369 100644 --- a/settings/cluster.go +++ b/settings/cluster.go @@ -3,3 +3,7 @@ package settings type Cluster struct { Node []string `ini:",,allowshadow"` } + +var ClusterSettings = Cluster{ + Node: []string{}, +} diff --git a/settings/cluster_test.go b/settings/cluster_test.go new file mode 100644 index 00000000..1d01ed0c --- /dev/null +++ b/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) +} diff --git a/settings/settings.go b/settings/settings.go index 7222d218..9ed09110 100644 --- a/settings/settings.go +++ b/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() + 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)) - } - } + // 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) } } - -