diff --git a/README-zh_CN.md b/README-zh_CN.md new file mode 100644 index 00000000..766cc884 --- /dev/null +++ b/README-zh_CN.md @@ -0,0 +1,93 @@ +# Nginx UI +Yet another Nginx Web UI + +Version: 1.1.0 + +[For English](README.md) + +## 项目特色 + +1. 可在线查看服务器 CPU、内存、load average、磁盘使用率等指标 +2. 可一键申请 Let's encrypt 证书 +3. 可自动续签 Let's encrypt 证书 +4. 在线编辑网站配置文件 + +## 项目预览 + +### 登录 +![](resources/screenshots/login.png) + +### 仪表盘 +![](resources/screenshots/dashboard.png) + +### 用户列表 +![](resources/screenshots/user-list.png) + +### 域名列表 +![](resources/screenshots/domain-list.png) + +### 域名编辑 +![](resources/screenshots/domain-edit.png) + +### 配置列表 +![](resources/screenshots/config-list.png) + +### 配置编辑 +![](resources/screenshots/config-edit.png) + +## 使用前注意 + +Nginx UI 遵循 Nginx 的标准,创建的网站配置文件位于 Nginx 配置目录(自动检测)下的 `sites-available` 目录, +启用后的网站的配置文件将会创建一份软连接到 `sites-enabled` 目录中。因此,您可能需要调整配置文件的组织方式。 + +## 安装 +1. 克隆项目 +``` +git clone https://github.com/0xJacky/nginx-ui +``` +2. 编译后端 +``` +cd server +go build -o nginx-ui-server main.go +``` +3. 启动后端 + 1. 前台启动 `./nginx-ui-server` + 2. 后台启动 `nohup ./nginx-ui-server &` + +4. 添加配置文件到 nginx +``` +server { + listen 80; + listen [::]:80; + + server_name ; + rewrite ^(.*)$ https://$host$1 permanent; +} + +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name ; + + ssl_certificate /path/to/ssl_cert; + ssl_certificate_key /path/to/ssl_cert_key; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection upgrade; + proxy_pass http://127.0.0.1:9000/; + } +} +``` + +4. 初始化系统 + +在浏览器中访问 `https:///install` + +输入用户名和密码创建初始账户。 diff --git a/README.md b/README.md index faa22571..a9d36605 100644 --- a/README.md +++ b/README.md @@ -3,56 +3,60 @@ Yet another Nginx Web UI Version: 1.1.0 -## 项目特色 +*Note: Currently only available in Simplified Chinese.* -1. 可在线查看服务器 CPU、内存、load average、磁盘使用率等指标 -2. 可一键申请 Let's encrypt 证书 -3. 可自动续签 Let's encrypt 证书 -4. 在线编辑网站配置文件 +[简体中文说明](README-zh_CN.md) -## 项目预览 +## Features -### 登录 -![](screenshots/login.png) +1. Online view of server CPU, Memory, Load Average, Disk Usage and other indicators. +2. One-click deployment Let's Encrypt certificates. +3. Automatic renewal Let's Encrypt certificates. +4. Online editing websites configuration files. -### 仪表盘 -![](screenshots/dashboard.png) +## Screenshots -### 用户列表 -![](screenshots/user-list.png) +### Login +![](resources/screenshots/login.png) -### 域名列表 -![](screenshots/domain-list.png) +### Dashboard +![](resources/screenshots/dashboard.png) -### 域名编辑 -![](screenshots/domain-edit.png) +### Users Management +![](resources/screenshots/user-list.png) -### 配置列表 -![](screenshots/config-list.png) +### Domains Management +![](resources/screenshots/domain-list.png) -### 配置编辑 -![](screenshots/config-edit.png) +### Domain Editor +![](resources/screenshots/domain-edit.png) -## 使用前注意 +### Configurations Management +![](resources/screenshots/config-list.png) -Nginx UI 遵循 Nginx 的标准,创建的网站配置文件位于 Nginx 配置目录(自动检测)下的 `sites-available` 目录, -启用后的网站的配置文件将会创建一份软连接到 `sites-enabled` 目录中。因此,您可能需要调整配置文件的组织方式。 +### Configuration Editor +![](resources/screenshots/config-edit.png) -## 安装 -1. 克隆项目 +## Note Before Use + +The Nginx UI follows the Nginx standard of creating site configuration files in the `sites-available` directory under the Nginx configuration directory (auto-detected). +The configuration files for an enabled site will create a soft link to the `sites-enabled` directory. Therefore, you may need to adjust the way the configuration files are organised. + +## Install +1. Clone ``` git clone https://github.com/0xJacky/nginx-ui ``` -2. 编译后端 +2. Compiling the backend ``` cd server go build -o nginx-ui-server main.go ``` -3. 启动后端 - 1. 前台启动 `./nginx-ui-server` - 2. 后台启动 `nohup ./nginx-ui-server &` +3. Start up the backend + 1. `./nginx-ui-server` for direct run. + 2. `nohup ./nginx-ui-server &` for run as service. -4. 添加配置文件到 nginx +4. Adding a configuration file to nginx ``` server { listen 80; @@ -84,8 +88,9 @@ server { } ``` -4. 初始化系统 +4. Installation -在浏览器中访问 `https:///install` +Visit `https:///install` in your browser. + +Enter your username and password to create initial account. -输入用户名和密码创建初始账户。 diff --git a/frontend/src/lib/utils/index.js b/frontend/src/lib/utils/index.js index 336cdee2..8334b108 100644 --- a/frontend/src/lib/utils/index.js +++ b/frontend/src/lib/utils/index.js @@ -36,7 +36,7 @@ export default { if (process.env.NODE_ENV === 'development' && process.env["VUE_APP_API_WSS_ROOT"]) { return process.env["VUE_APP_API_WSS_ROOT"] } - return protocol + location.host + '/' + process.env["VUE_APP_API_WSS_ROOT"] + return protocol + location.host + process.env["VUE_APP_API_WSS_ROOT"] } } } diff --git a/frontend/src/views/other/Install.vue b/frontend/src/views/other/Install.vue index a88144f1..a0db6909 100644 --- a/frontend/src/views/other/Install.vue +++ b/frontend/src/views/other/Install.vue @@ -22,7 +22,7 @@ message: 'Please input your E-mail!', },] }, ]" - placeholder="Email" + placeholder="Email (*)" > @@ -33,7 +33,7 @@ 'username', { rules: [{ required: true, message: 'Please input your username!' }] }, ]" - placeholder="Username" + placeholder="Username (*)" > @@ -45,11 +45,22 @@ { rules: [{ required: true, message: 'Please input your Password!' }] }, ]" type="password" - placeholder="Password" + placeholder="Password (*)" > + + + + + 安装 @@ -57,7 +68,7 @@
- Copyright © 2020 - {{ thisYear }} 0xJacky + Copyright © 2020 - {{ thisYear }} Nginx UI
diff --git a/frontend/src/views/other/Login.vue b/frontend/src/views/other/Login.vue index 3ae145e0..4312e141 100644 --- a/frontend/src/views/other/Login.vue +++ b/frontend/src/views/other/Login.vue @@ -39,7 +39,7 @@ diff --git a/frontend/version.json b/frontend/version.json index 07c4a3a8..19993a64 100644 --- a/frontend/version.json +++ b/frontend/version.json @@ -1 +1 @@ -{"version":"1.1.0","build_id":5,"total_build":22} \ No newline at end of file +{"version":"1.1.0","build_id":8,"total_build":25} \ No newline at end of file diff --git a/main.go b/main.go index 1effefd9..9954b570 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "github.com/0xJacky/Nginx-UI/server/settings" tool2 "github.com/0xJacky/Nginx-UI/server/tool" "log" + "mime" "net/http" "os/signal" "syscall" @@ -19,22 +20,26 @@ func main() { ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() - var dataDir string - flag.StringVar(&dataDir, "d", ".", "Specify the data dir") + // 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") + + var confPath string + flag.StringVar(&confPath, "config", "app.ini", "Specify the configuration file") flag.Parse() - settings.Init(dataDir) - model.Init() + settings.Init(confPath) + log.Printf("nginx config dir path: %s", tool2.GetNginxConfPath("")) + if "" != settings.ServerSettings.JwtSecret { + model.Init() + go tool2.AutoCert() + } srv := &http.Server{ Addr: ":" + settings.ServerSettings.HttpPort, Handler: router.InitRouter(), } - log.Printf("nginx config dir path: %s", tool2.GetNginxConfPath("")) - - go tool2.AutoCert() - // Initializing the server in a goroutine so that // it won't block the graceful shutdown handling below go func() { diff --git a/nginx-ui.service b/nginx-ui.service index 9cd7c4a3..332845c6 100644 --- a/nginx-ui.service +++ b/nginx-ui.service @@ -5,7 +5,7 @@ After=network.target [Service] Type=simple -ExecStart=/usr/local/bin/nginx-ui -d /usr/local/etc/nginx-ui +ExecStart=/usr/local/bin/nginx-ui -d /usr/local/etc/nginx-ui/app.ini Restart=on-failure TimeoutStopSec=5 KillMode=mixed diff --git a/logo.png b/resources/logo.png similarity index 100% rename from logo.png rename to resources/logo.png diff --git a/nginx-ui-logo-design.sketch b/resources/nginx-ui-logo-design.sketch similarity index 100% rename from nginx-ui-logo-design.sketch rename to resources/nginx-ui-logo-design.sketch diff --git a/screenshots/config-edit.png b/resources/screenshots/config-edit.png similarity index 100% rename from screenshots/config-edit.png rename to resources/screenshots/config-edit.png diff --git a/screenshots/config-list.png b/resources/screenshots/config-list.png similarity index 100% rename from screenshots/config-list.png rename to resources/screenshots/config-list.png diff --git a/screenshots/dashboard.png b/resources/screenshots/dashboard.png similarity index 100% rename from screenshots/dashboard.png rename to resources/screenshots/dashboard.png diff --git a/screenshots/domain-edit.png b/resources/screenshots/domain-edit.png similarity index 100% rename from screenshots/domain-edit.png rename to resources/screenshots/domain-edit.png diff --git a/screenshots/domain-list.png b/resources/screenshots/domain-list.png similarity index 100% rename from screenshots/domain-list.png rename to resources/screenshots/domain-list.png diff --git a/screenshots/login.png b/resources/screenshots/login.png similarity index 100% rename from screenshots/login.png rename to resources/screenshots/login.png diff --git a/screenshots/user-list.png b/resources/screenshots/user-list.png similarity index 100% rename from screenshots/user-list.png rename to resources/screenshots/user-list.png diff --git a/server/api/install.go b/server/api/install.go index 9418507f..06c6f76d 100644 --- a/server/api/install.go +++ b/server/api/install.go @@ -1,22 +1,17 @@ package api import ( - model2 "github.com/0xJacky/Nginx-UI/server/model" + "github.com/0xJacky/Nginx-UI/server/model" "github.com/0xJacky/Nginx-UI/server/settings" + "github.com/0xJacky/Nginx-UI/server/tool" "github.com/gin-gonic/gin" "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "net/http" - "os" - "path" ) func installLockStatus() bool { - lockPath := path.Join(settings.DataDir, "app.ini") - _, err := os.Stat(lockPath) - - return !os.IsNotExist(err) - + return "" != settings.ServerSettings.JwtSecret } func InstallLockCheck(c *gin.Context) { @@ -29,6 +24,7 @@ type InstallJson struct { Email string `json:"email" binding:"required,email"` Username string `json:"username" binding:"required,max=255"` Password string `json:"password" binding:"required,max=255"` + Database string `json:"database"` } func InstallNginxUI(c *gin.Context) { @@ -45,18 +41,26 @@ func InstallNginxUI(c *gin.Context) { return } - serverSettings := settings.Conf.Section("server") - serverSettings.Key("JwtSecret").SetValue(uuid.New().String()) - serverSettings.Key("Email").SetValue(json.Email) + settings.ServerSettings.JwtSecret = uuid.New().String() + settings.ServerSettings.Email = json.Email + if "" != json.Database { + settings.ServerSettings.Database = json.Database + } + settings.ReflectFrom() + err := settings.Save() if err != nil { ErrHandler(c, err) return } - curd := model2.NewCurd(&model2.Auth{}) + // Init model and auto cert + model.Init() + go tool.AutoCert() + + curd := model.NewCurd(&model.Auth{}) pwd, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost) - err = curd.Add(&model2.Auth{ + err = curd.Add(&model.Auth{ Name: json.Username, Password: string(pwd), }) diff --git a/server/model/models.go b/server/model/models.go index 27849185..2c4871a8 100644 --- a/server/model/models.go +++ b/server/model/models.go @@ -1,6 +1,7 @@ package model import ( + "fmt" "github.com/0xJacky/Nginx-UI/server/settings" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -20,7 +21,7 @@ type Model struct { } func Init() { - dbPath := path.Join(settings.DataDir, "database.db") + dbPath := path.Join(path.Dir(settings.ConfPath), fmt.Sprintf("%s.db", settings.ServerSettings.Database)) var err error db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), diff --git a/server/router/routers.go b/server/router/routers.go index 65c12967..516bf95c 100644 --- a/server/router/routers.go +++ b/server/router/routers.go @@ -3,6 +3,7 @@ package router import ( "bufio" api2 "github.com/0xJacky/Nginx-UI/server/api" + "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "net/http" "strings" @@ -14,7 +15,7 @@ func InitRouter() *gin.Engine { r.Use(gin.Recovery()) - r.Use(tryStatic("/", mustFS(""))) + r.Use(static.Serve("/", mustFS(""))) r.NoRoute(func(c *gin.Context) { accept := c.Request.Header.Get("Accept") diff --git a/server/settings/settings.go b/server/settings/settings.go index 22636e12..ba0ad6e7 100644 --- a/server/settings/settings.go +++ b/server/settings/settings.go @@ -1,10 +1,8 @@ package settings import ( - "gopkg.in/ini.v1" - "log" - "os" - "path" + "gopkg.in/ini.v1" + "log" ) var Conf *ini.File @@ -16,30 +14,47 @@ type Server struct { JwtSecret string HTTPChallengePort string Email string + Database string } -var ServerSettings = &Server{} - -var DataDir string -var confPath string - -func Init(dataDir string) { - DataDir = dataDir - confPath = path.Join(dataDir, "app.ini") - if _, err := os.Stat(confPath); os.IsNotExist(err) { - confPath = path.Join(dataDir, "app.example.ini") - } - Setup() +var ServerSettings = &Server{ + HttpPort: "9000", + RunMode: "debug", + HTTPChallengePort: "9180", + Database: "database", } -func Setup() { - var err error - Conf, err = ini.Load(confPath) - if err != nil { - log.Fatalf("setting.Setup, fail to parse '%s': %v", confPath, err) - } +var ConfPath string - mapTo("server", ServerSettings) +var sections = map[string]interface{}{ + "server": ServerSettings, +} + +func Init(confPath string) { + ConfPath = confPath + Setup() +} + +func Setup() { + var err error + Conf, err = ini.LooseLoad(ConfPath) + if err != nil { + log.Printf("setting.Setup: %v", err) + } else { + MapTo() + } +} + +func MapTo() { + for k, v := range sections { + mapTo(k, v) + } +} + +func ReflectFrom() { + for k, v := range sections { + reflectFrom(k, v) + } } func mapTo(section string, v interface{}) { @@ -49,12 +64,18 @@ func mapTo(section string, v interface{}) { } } -func Save() (err error) { - confPath = path.Join(DataDir, "app.ini") - err = Conf.SaveTo(confPath) - if err != nil { - return - } - Setup() - return +func reflectFrom(section string, v interface{}) { + err := Conf.Section(section).ReflectFrom(v) + if err != nil { + log.Fatalf("Cfg.ReflectFrom %s err: %v", section, err) + } +} + +func Save() (err error) { + err = Conf.SaveTo(ConfPath) + if err != nil { + return + } + Setup() + return }