From 26284a008c5c4983a4c6671ebeca5b5dc75cdf51 Mon Sep 17 00:00:00 2001 From: Jacky Date: Mon, 6 May 2024 22:55:05 +0800 Subject: [PATCH] feat: use env to predefine admin user #214 --- api/system/install.go | 2 +- api/system/settings.go | 2 - app.example.ini | 1 + internal/kernal/boot.go | 167 ++++++++++++++++---------------- internal/kernal/skip_install.go | 74 ++++++++++++++ resources/docker/nginx-ui.run | 3 +- settings/settings.go | 10 +- settings/settings_test.go | 2 + settings/user.go | 8 -- 9 files changed, 165 insertions(+), 104 deletions(-) create mode 100644 internal/kernal/skip_install.go delete mode 100644 settings/user.go diff --git a/api/system/install.go b/api/system/install.go index 9130ef3f..49340fd5 100644 --- a/api/system/install.go +++ b/api/system/install.go @@ -49,7 +49,6 @@ func InstallNginxUI(c *gin.Context) { if "" != json.Database { settings.ServerSettings.Database = json.Database } - settings.ReflectFrom() err := settings.Save() if err != nil { @@ -72,6 +71,7 @@ func InstallNginxUI(c *gin.Context) { api.ErrHandler(c, err) return } + c.JSON(http.StatusOK, gin.H{ "message": "ok", }) diff --git a/api/system/settings.go b/api/system/settings.go index 5e18dff9..b09bc3c3 100644 --- a/api/system/settings.go +++ b/api/system/settings.go @@ -40,8 +40,6 @@ func SaveSettings(c *gin.Context) { fillSettings(&settings.OpenAISettings, &json.Openai) fillSettings(&settings.LogrotateSettings, &json.Logrotate) - settings.ReflectFrom() - err := settings.Save() if err != nil { api.ErrHandler(c, err) diff --git a/app.example.ini b/app.example.ini index ae9c8270..f0e97bcf 100644 --- a/app.example.ini +++ b/app.example.ini @@ -14,6 +14,7 @@ PageSize = 10 HttpHost = 0.0.0.0 CertRenewalInterval = 7 RecursiveNameservers = +SkipInstallation = false [nginx] AccessLogPath = /var/log/nginx/access.log diff --git a/internal/kernal/boot.go b/internal/kernal/boot.go index 5f331188..c226d057 100644 --- a/internal/kernal/boot.go +++ b/internal/kernal/boot.go @@ -1,116 +1,111 @@ package kernal import ( - "github.com/0xJacky/Nginx-UI/internal/analytic" - "github.com/0xJacky/Nginx-UI/internal/cert" - "github.com/0xJacky/Nginx-UI/internal/logger" - "github.com/0xJacky/Nginx-UI/internal/logrotate" - "github.com/0xJacky/Nginx-UI/internal/validation" - "github.com/0xJacky/Nginx-UI/model" - "github.com/0xJacky/Nginx-UI/query" - "github.com/0xJacky/Nginx-UI/settings" - "github.com/go-co-op/gocron" - "github.com/google/uuid" - "mime" - "runtime" - "time" + "github.com/0xJacky/Nginx-UI/internal/analytic" + "github.com/0xJacky/Nginx-UI/internal/cert" + "github.com/0xJacky/Nginx-UI/internal/logger" + "github.com/0xJacky/Nginx-UI/internal/logrotate" + "github.com/0xJacky/Nginx-UI/internal/validation" + "github.com/0xJacky/Nginx-UI/model" + "github.com/0xJacky/Nginx-UI/query" + "github.com/0xJacky/Nginx-UI/settings" + "github.com/go-co-op/gocron" + "github.com/google/uuid" + "mime" + "runtime" + "time" ) func Boot() { - defer recovery() - - async := []func(){ - InitJsExtensionType, - InitDatabase, - InitNodeSecret, - validation.Init, - } - - syncs := []func(){ - analytic.RecordServerAnalytic, - } - - for _, v := range async { - v() - } - - for _, v := range syncs { - go v() - } + defer recovery() + + async := []func(){ + InitJsExtensionType, + InitDatabase, + InitNodeSecret, + validation.Init, + } + + syncs := []func(){ + analytic.RecordServerAnalytic, + } + + for _, v := range async { + v() + } + + for _, v := range syncs { + go v() + } } func InitAfterDatabase() { - syncs := []func(){ - cert.InitRegister, - InitCronJobs, - analytic.RetrieveNodesStatus, - } - - for _, v := range syncs { - go v() - } + syncs := []func(){ + registerPredefinedUser, + cert.InitRegister, + InitCronJobs, + analytic.RetrieveNodesStatus, + } + + for _, v := range syncs { + go v() + } } func recovery() { - if err := recover(); err != nil { - buf := make([]byte, 1024) - runtime.Stack(buf, false) - logger.Errorf("%s\n%s", err, buf) - } + if err := recover(); err != nil { + buf := make([]byte, 1024) + runtime.Stack(buf, false) + logger.Errorf("%s\n%s", err, buf) + } } func InitDatabase() { + // Skip install + if settings.ServerSettings.SkipInstallation { + skipInstall() + } - // Skip installation - if settings.ServerSettings.SkipInstallation && settings.ServerSettings.JwtSecret == "" { - settings.ServerSettings.JwtSecret = uuid.New().String() - err := settings.Save() - if err != nil { - logger.Error(err) - } - } - - if "" != settings.ServerSettings.JwtSecret { - db := model.Init() - query.Init(db) - - InitAfterDatabase() - } + if "" != settings.ServerSettings.JwtSecret { + db := model.Init() + query.Init(db) + + InitAfterDatabase() + } } func InitNodeSecret() { - if "" == settings.ServerSettings.NodeSecret { - logger.Warn("NodeSecret is empty, generating...") - settings.ServerSettings.NodeSecret = uuid.New().String() - settings.ReflectFrom() - - err := settings.Save() - if err != nil { - logger.Error("Error save settings") - } - logger.Warn("Generated NodeSecret: ", settings.ServerSettings.NodeSecret) - } + if "" == settings.ServerSettings.NodeSecret { + logger.Warn("NodeSecret is empty, generating...") + settings.ServerSettings.NodeSecret = uuid.New().String() + + err := settings.Save() + if err != nil { + logger.Error("Error save settings") + } + logger.Warn("Generated NodeSecret: ", settings.ServerSettings.NodeSecret) + } } func InitJsExtensionType() { - // Hack: fix wrong Content Type of .js file on some OS platforms - // See https://github.com/golang/go/issues/32350 - _ = mime.AddExtensionType(".js", "text/javascript; charset=utf-8") + // Hack: fix wrong Content Type of .js file on some OS platforms + // See https://github.com/golang/go/issues/32350 + _ = mime.AddExtensionType(".js", "text/javascript; charset=utf-8") } func InitCronJobs() { - s := gocron.NewScheduler(time.UTC) - job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoCert) + s := gocron.NewScheduler(time.UTC) + job, err := s.Every(30).Minute().SingletonMode().Do(cert.AutoCert) - if err != nil { - logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err) - } + if err != nil { + logger.Fatalf("AutoCert Job: %v, Err: %v\n", job, err) + } - job, err = s.Every(settings.LogrotateSettings.Interval).Minute().SingletonMode().Do(logrotate.Exec) + job, err = s.Every(settings.LogrotateSettings.Interval).Minute().SingletonMode().Do(logrotate.Exec) - if err != nil { - logger.Fatalf("LogRotate Job: %v, Err: %v\n", job, err) - } + if err != nil { + logger.Fatalf("LogRotate Job: %v, Err: %v\n", job, err) + } - s.StartAsync() + s.StartAsync() } diff --git a/internal/kernal/skip_install.go b/internal/kernal/skip_install.go new file mode 100644 index 00000000..ad2e0326 --- /dev/null +++ b/internal/kernal/skip_install.go @@ -0,0 +1,74 @@ +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" + "github.com/caarlos0/env/v11" + "github.com/google/uuid" + "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type predefinedUser struct { + Name string `json:"name"` + Password string `json:"password"` +} + +func skipInstall() { + logger.Info("Skip installation mode enabled") + + if settings.ServerSettings.JwtSecret == "" { + settings.ServerSettings.JwtSecret = uuid.New().String() + } + + if settings.ServerSettings.NodeSecret == "" { + settings.ServerSettings.NodeSecret = uuid.New().String() + logger.Infof("NodeSecret: %s", settings.ServerSettings.NodeSecret) + } + + err := settings.Save() + if err != nil { + logger.Fatal(err) + } +} + +func registerPredefinedUser() { + // when skip installation mode is enabled, the predefined user will be created + if !settings.ServerSettings.SkipInstallation { + return + } + pUser := &predefinedUser{} + + err := env.ParseWithOptions(pUser, env.Options{ + Prefix: "NGINX_UI_PREDEFINED_USER_", + UseFieldNameByDefault: true, + }) + + if err != nil { + logger.Fatal(err) + } + + u := query.Auth + + _, err = u.First() + + // Only effect when there is no user in the database + if !errors.Is(err, gorm.ErrRecordNotFound) || pUser.Name == "" || pUser.Password == "" { + return + } + + // Create a new user with the predefined name and password + pwd, _ := bcrypt.GenerateFromPassword([]byte(pUser.Password), bcrypt.DefaultCost) + + err = u.Create(&model.Auth{ + Name: pUser.Name, + Password: string(pwd), + }) + + if err != nil { + logger.Error(err) + } +} diff --git a/resources/docker/nginx-ui.run b/resources/docker/nginx-ui.run index 835988ce..43470252 100644 --- a/resources/docker/nginx-ui.run +++ b/resources/docker/nginx-ui.run @@ -1,2 +1,3 @@ -#!/bin/sh +#!/command/with-contenv sh +env nginx-ui --config /etc/nginx-ui/app.ini diff --git a/settings/settings.go b/settings/settings.go index 544d3aeb..0ae436a5 100644 --- a/settings/settings.go +++ b/settings/settings.go @@ -69,12 +69,6 @@ func MapTo() { } } -func ReflectFrom() { - for k, v := range sections { - reflectFrom(k, v) - } -} - func mapTo(section string, v interface{}) { err := Conf.Section(section).MapTo(v) if err != nil { @@ -90,6 +84,10 @@ func reflectFrom(section string, v interface{}) { } func Save() (err error) { + for k, v := range sections { + reflectFrom(k, v) + } + err = Conf.SaveTo(ConfPath) if err != nil { return diff --git a/settings/settings_test.go b/settings/settings_test.go index b50f3730..93f815f1 100644 --- a/settings/settings_test.go +++ b/settings/settings_test.go @@ -52,6 +52,7 @@ func TestSetup(t *testing.T) { _ = os.Setenv("NGINX_UI_LOGROTATE_CMD", "logrotate /custom/logrotate.conf") _ = os.Setenv("NGINX_UI_LOGROTATE_INTERVAL", "60") + ConfPath = "app.testing.ini" Setup() assert.Equal(t, "8080", ServerSettings.HttpPort) @@ -96,4 +97,5 @@ func TestSetup(t *testing.T) { assert.Equal(t, 60, LogrotateSettings.Interval) os.Clearenv() + _ = os.Remove("app.testing.ini") } diff --git a/settings/user.go b/settings/user.go deleted file mode 100644 index 1cf99137..00000000 --- a/settings/user.go +++ /dev/null @@ -1,8 +0,0 @@ -package settings - -type PredefinedUser struct { - User string `json:"user"` - Password string `json:"password"` -} - -var PredefinedUserSettings = &PredefinedUser{}