1
0
0xJacky 3 жил өмнө
parent
commit
d09f484790
86 өөрчлөгдсөн 884 нэмэгдсэн , 8974 устгасан
  1. 1 0
      .gitignore
  2. 6 12
      README.md
  3. 0 0
      api/analytic.go
  4. 0 0
      api/api.go
  5. 57 0
      api/auth.go
  6. 3 3
      api/backup.go
  7. 1 1
      api/cert.go
  8. 1 1
      api/config.go
  9. 263 0
      api/domain.go
  10. 2 2
      api/install.go
  11. 37 0
      api/template.go
  12. 1 1
      api/user.go
  13. 0 0
      app.example.ini
  14. BIN
      frontend/dist/favicon.ico
  15. BIN
      frontend/dist/img/logo.9e691c6b.png
  16. 0 8115
      frontend/dist/img/remixicon.symbol.f09b1c74.svg
  17. 0 0
      frontend/dist/index.html
  18. 0 0
      frontend/dist/js/chunk-00be396e-legacy.f6afc813.js
  19. 0 0
      frontend/dist/js/chunk-00be396e.f6afc813.js
  20. 0 1
      frontend/dist/js/chunk-0393876a-legacy.0e2e8183.js
  21. 0 1
      frontend/dist/js/chunk-0393876a.0e2e8183.js
  22. 0 0
      frontend/dist/js/chunk-05148b16-legacy.66291bd9.js
  23. 0 0
      frontend/dist/js/chunk-05148b16.66291bd9.js
  24. 0 1
      frontend/dist/js/chunk-09f0acda-legacy.b788b1ae.js
  25. 0 1
      frontend/dist/js/chunk-09f0acda.b788b1ae.js
  26. 0 0
      frontend/dist/js/chunk-2881409a-legacy.1f853726.js
  27. 0 0
      frontend/dist/js/chunk-2881409a.1f853726.js
  28. 0 1
      frontend/dist/js/chunk-2d0cf277-legacy.c260d8d5.js
  29. 0 1
      frontend/dist/js/chunk-2d0cf277.c260d8d5.js
  30. 0 0
      frontend/dist/js/chunk-312c57da-legacy.53fa07de.js
  31. 0 0
      frontend/dist/js/chunk-312c57da.53fa07de.js
  32. 0 0
      frontend/dist/js/chunk-4216c952-legacy.f0073bdb.js
  33. 0 0
      frontend/dist/js/chunk-4216c952.f0073bdb.js
  34. 0 0
      frontend/dist/js/chunk-46dcb584-legacy.5ee0f4ea.js
  35. 0 0
      frontend/dist/js/chunk-46dcb584.5ee0f4ea.js
  36. 0 0
      frontend/dist/js/chunk-4f82bf3d-legacy.8d3be338.js
  37. 0 0
      frontend/dist/js/chunk-4f82bf3d.8d3be338.js
  38. 0 0
      frontend/dist/js/chunk-5573b71a-legacy.92b99af4.js
  39. 0 0
      frontend/dist/js/chunk-5573b71a.92b99af4.js
  40. 0 7
      frontend/dist/js/chunk-5d4d188e-legacy.b0ffa164.js
  41. 0 7
      frontend/dist/js/chunk-5d4d188e.b0ffa164.js
  42. 0 0
      frontend/dist/js/chunk-6a4ca29d-legacy.5593b7e1.js
  43. 0 0
      frontend/dist/js/chunk-6a4ca29d.5593b7e1.js
  44. 0 0
      frontend/dist/js/chunk-83d83096-legacy.72980dc3.js
  45. 0 0
      frontend/dist/js/chunk-83d83096.72980dc3.js
  46. 0 0
      frontend/dist/js/chunk-b508de6a-legacy.b421b1eb.js
  47. 0 0
      frontend/dist/js/chunk-b508de6a.b421b1eb.js
  48. 0 0
      frontend/dist/js/chunk-c8c0a686-legacy.85e5c7a1.js
  49. 0 0
      frontend/dist/js/chunk-c8c0a686.85e5c7a1.js
  50. 0 0
      frontend/dist/js/chunk-vendors-legacy.731e48fc.js
  51. 0 0
      frontend/dist/js/chunk-vendors.731e48fc.js
  52. 0 0
      frontend/dist/js/index-legacy.ec1dfd27.js
  53. 0 0
      frontend/dist/js/index.62a46eff.js
  54. 0 1
      frontend/dist/version.json
  55. 6 0
      frontend/frontend.go
  56. 1 1
      frontend/version.json
  57. 3 2
      go.mod
  58. 4 0
      go.sum
  59. 32 0
      main.go
  60. 63 0
      model/auth.go
  61. 0 0
      model/cert.go
  62. 0 0
      model/config-backup.go
  63. 0 0
      model/curd.go
  64. 0 0
      model/log.go
  65. 1 1
      model/models.go
  66. 134 0
      router/routers.go
  67. 0 57
      server/api/auth.go
  68. 0 263
      server/api/domain.go
  69. 0 37
      server/api/template.go
  70. 0 32
      server/main.go
  71. 0 63
      server/model/auth.go
  72. 0 94
      server/router/routers.go
  73. 0 59
      server/test/acme_test.go
  74. 0 40
      server/test/cert_test.go
  75. 0 169
      server/tool/cert.go
  76. 0 0
      settings/settings.go
  77. 0 0
      template/http-conf
  78. 0 0
      template/https-conf
  79. 59 0
      test/acme_test.go
  80. 0 0
      test/analytic_test.go
  81. 40 0
      test/cert_test.go
  82. 0 0
      test/lego_test.go
  83. 0 0
      test/nginx_test.go
  84. 169 0
      tool/cert.go
  85. 0 0
      tool/config_list.go
  86. 0 0
      tool/nginx.go

+ 1 - 0
.gitignore

@@ -4,3 +4,4 @@ database.db
 tmp
 node_modules
 app.ini
+dist

+ 6 - 12
README.md

@@ -74,21 +74,15 @@ server {
     root	/path/to/nginx-ui/frontend/dist;
 
     location / {
-        # First attempt to serve request as file, then
-        # as directory, then fall back to displaying a 404.
-        index index.html;
-        try_files $uri $uri/ /index.html;
-    }
-
-    location /api/ {
-        proxy_set_header Host $host;
-        proxy_set_header X-Real_IP $remote_addr;
-        proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
+		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/;
-    }
+		proxy_pass http://127.0.0.1:9000/;
+	}
 }
 ```
 

+ 0 - 0
server/api/analytic.go → api/analytic.go


+ 0 - 0
server/api/api.go → api/api.go


+ 57 - 0
api/auth.go

@@ -0,0 +1,57 @@
+package api
+
+import (
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/gin-gonic/gin"
+	"golang.org/x/crypto/bcrypt"
+	"net/http"
+)
+
+type LoginUser struct {
+	Name     string `json:"name" binding:"required,max=255"`
+	Password string `json:"password" binding:"required,max=255"`
+}
+
+func Login(c *gin.Context) {
+	var user LoginUser
+	ok := BindAndValid(c, &user)
+	if !ok {
+		return
+	}
+
+	u, _ := model.GetUser(user.Name)
+
+	if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
+		c.JSON(http.StatusForbidden, gin.H{
+			"message": "用户名或密码错误",
+		})
+		return
+	}
+
+	token, err := model.GenerateJWT(u.Name)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+		"token":   token,
+	})
+}
+
+func Logout(c *gin.Context) {
+	token := c.GetHeader("Authorization")
+	if token != "" {
+		err := model.DeleteToken(token)
+		if err != nil {
+			c.JSON(http.StatusInternalServerError, gin.H{
+				"message": err.Error(),
+			})
+			return
+		}
+	}
+	c.JSON(http.StatusNoContent, nil)
+}

+ 3 - 3
server/api/backup.go → api/backup.go

@@ -1,13 +1,13 @@
 package api
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/gin-gonic/gin"
 	"github.com/unknwon/com"
 	"net/http"
 )
 
-func GetFileBackupList(c *gin.Context)  {
+func GetFileBackupList(c *gin.Context) {
 	path := c.Query("path")
 	backups := model.GetBackupList(path)
 
@@ -16,7 +16,7 @@ func GetFileBackupList(c *gin.Context)  {
 	})
 }
 
-func GetFileBackup(c *gin.Context)  {
+func GetFileBackup(c *gin.Context) {
 	id := c.Param("id")
 	backup := model.GetBackup(com.StrTo(id).MustInt())
 

+ 1 - 1
server/api/cert.go → api/cert.go

@@ -2,7 +2,7 @@ package api
 
 import (
 	"encoding/json"
-	"github.com/0xJacky/Nginx-UI/server/tool"
+	"github.com/0xJacky/Nginx-UI/tool"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
 	"log"

+ 1 - 1
server/api/config.go → api/config.go

@@ -1,7 +1,7 @@
 package api
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/tool"
+	"github.com/0xJacky/Nginx-UI/tool"
 	"github.com/gin-gonic/gin"
 	"io/ioutil"
 	"log"

+ 263 - 0
api/domain.go

@@ -0,0 +1,263 @@
+package api
+
+import (
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/tool"
+	"github.com/gin-gonic/gin"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+)
+
+func GetDomains(c *gin.Context) {
+	orderBy := c.Query("order_by")
+	sort := c.DefaultQuery("sort", "desc")
+
+	mySort := map[string]string{
+		"enabled": "bool",
+		"name":    "string",
+		"modify":  "time",
+	}
+
+	configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
+
+	enabledConfigMap := make(map[string]bool)
+	for i := range enabledConfig {
+		enabledConfigMap[enabledConfig[i].Name()] = true
+	}
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	var configs []gin.H
+
+	for i := range configFiles {
+		file := configFiles[i]
+		if !file.IsDir() {
+			configs = append(configs, gin.H{
+				"name":    file.Name(),
+				"size":    file.Size(),
+				"modify":  file.ModTime(),
+				"enabled": enabledConfigMap[file.Name()],
+			})
+		}
+	}
+
+	configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
+
+	c.JSON(http.StatusOK, gin.H{
+		"configs": configs,
+	})
+}
+
+func GetDomain(c *gin.Context) {
+	name := c.Param("name")
+	path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
+
+	enabled := true
+	if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
+		enabled = false
+	}
+
+	content, err := ioutil.ReadFile(path)
+
+	if err != nil {
+		if os.IsNotExist(err) {
+			c.JSON(http.StatusNotFound, gin.H{
+				"message": err.Error(),
+			})
+			return
+		}
+		ErrHandler(c, err)
+		return
+	}
+
+	_, err = model.FirstCert(name)
+
+	c.JSON(http.StatusOK, gin.H{
+		"enabled":   enabled,
+		"name":      name,
+		"config":    string(content),
+		"auto_cert": err == nil,
+	})
+
+}
+
+func EditDomain(c *gin.Context) {
+	var err error
+	name := c.Param("name")
+	request := make(gin.H)
+	err = c.BindJSON(&request)
+	path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
+
+	err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
+	if _, err = os.Stat(enabledConfigFilePath); err == nil {
+		// 测试配置文件
+		err = tool.TestNginxConf(enabledConfigFilePath)
+		if err != nil {
+			c.JSON(http.StatusInternalServerError, gin.H{
+				"message": err.Error(),
+			})
+			return
+		}
+
+		output := tool.ReloadNginx()
+
+		if output != "" {
+			c.JSON(http.StatusInternalServerError, gin.H{
+				"message": output,
+			})
+			return
+		}
+	}
+
+	GetDomain(c)
+}
+
+func EnableDomain(c *gin.Context) {
+	configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
+	enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
+
+	_, err := os.Stat(configFilePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.Symlink(configFilePath, enabledConfigFilePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	// 测试配置文件,不通过则撤回启用
+	err = tool.TestNginxConf(enabledConfigFilePath)
+	if err != nil {
+		_ = os.Remove(enabledConfigFilePath)
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": err.Error(),
+		})
+		return
+	}
+
+	output := tool.ReloadNginx()
+
+	if output != "" {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": output,
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
+}
+
+func DisableDomain(c *gin.Context) {
+	enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
+
+	_, err := os.Stat(enabledConfigFilePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	err = os.Remove(enabledConfigFilePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	output := tool.ReloadNginx()
+
+	if output != "" {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": output,
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
+}
+
+func DeleteDomain(c *gin.Context) {
+	var err error
+	name := c.Param("name")
+	availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
+	enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
+
+	if _, err = os.Stat(availablePath); os.IsNotExist(err) {
+		c.JSON(http.StatusNotFound, gin.H{
+			"message": "site not found",
+		})
+		return
+	}
+
+	if _, err = os.Stat(enabledPath); err == nil {
+		c.JSON(http.StatusNotAcceptable, gin.H{
+			"message": "site is enabled",
+		})
+		return
+	}
+
+	cert := model.Cert{Domain: name}
+	_ = cert.Remove()
+
+	err = os.Remove(availablePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
+
+}
+
+func AddDomainToAutoCert(c *gin.Context) {
+	domain := c.Param("domain")
+	cert, err := model.FirstOrCreateCert(domain)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, cert)
+}
+
+func RemoveDomainFromAutoCert(c *gin.Context) {
+	cert := model.Cert{
+		Domain: c.Param("domain"),
+	}
+	err := cert.Remove()
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, nil)
+}

+ 2 - 2
server/api/install.go → api/install.go

@@ -1,8 +1,8 @@
 package api
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/settings"
 	"github.com/gin-gonic/gin"
 	"github.com/google/uuid"
 	"golang.org/x/crypto/bcrypt"

+ 37 - 0
api/template.go

@@ -0,0 +1,37 @@
+package api
+
+import (
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/gin-gonic/gin"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func GetTemplate(c *gin.Context) {
+	name := c.Param("name")
+	path := filepath.Join("template", name)
+	content, err := ioutil.ReadFile(path)
+
+	_content := string(content)
+	_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
+		settings.ServerSettings.HTTPChallengePort)
+
+	if err != nil {
+		if os.IsNotExist(err) {
+			c.JSON(http.StatusNotFound, gin.H{
+				"message": err.Error(),
+			})
+			return
+		}
+		ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message":  "ok",
+		"template": _content,
+	})
+}

+ 1 - 1
server/api/user.go → api/user.go

@@ -2,7 +2,7 @@ package api
 
 import (
 	"errors"
-	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/gin-gonic/gin"
 	"github.com/spf13/cast"
 	"golang.org/x/crypto/bcrypt"

+ 0 - 0
server/app.example.ini → app.example.ini


BIN
frontend/dist/favicon.ico


BIN
frontend/dist/img/logo.9e691c6b.png


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 8115
frontend/dist/img/remixicon.symbol.f09b1c74.svg


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/index.html


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-00be396e-legacy.f6afc813.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-00be396e.f6afc813.js


+ 0 - 1
frontend/dist/js/chunk-0393876a-legacy.0e2e8183.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);

+ 0 - 1
frontend/dist/js/chunk-0393876a.0e2e8183.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-05148b16-legacy.66291bd9.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-05148b16.66291bd9.js


+ 0 - 1
frontend/dist/js/chunk-09f0acda-legacy.b788b1ae.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);

+ 0 - 1
frontend/dist/js/chunk-09f0acda.b788b1ae.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-2881409a-legacy.1f853726.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-2881409a.1f853726.js


+ 0 - 1
frontend/dist/js/chunk-2d0cf277-legacy.c260d8d5.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);

+ 0 - 1
frontend/dist/js/chunk-2d0cf277.c260d8d5.js

@@ -1 +0,0 @@
-(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-312c57da-legacy.53fa07de.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-312c57da.53fa07de.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-4216c952-legacy.f0073bdb.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-4216c952.f0073bdb.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-46dcb584-legacy.5ee0f4ea.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-46dcb584.5ee0f4ea.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-4f82bf3d-legacy.8d3be338.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-4f82bf3d.8d3be338.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-5573b71a-legacy.92b99af4.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-5573b71a.92b99af4.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 7
frontend/dist/js/chunk-5d4d188e-legacy.b0ffa164.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 7
frontend/dist/js/chunk-5d4d188e.b0ffa164.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-6a4ca29d-legacy.5593b7e1.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-6a4ca29d.5593b7e1.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-83d83096-legacy.72980dc3.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-83d83096.72980dc3.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-b508de6a-legacy.b421b1eb.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-b508de6a.b421b1eb.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-c8c0a686-legacy.85e5c7a1.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-c8c0a686.85e5c7a1.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-vendors-legacy.731e48fc.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/chunk-vendors.731e48fc.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/index-legacy.ec1dfd27.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
frontend/dist/js/index.62a46eff.js


+ 0 - 1
frontend/dist/version.json

@@ -1 +0,0 @@
-{"version":"1.1.0","build_id":2,"total_build":19}

+ 6 - 0
frontend/frontend.go

@@ -0,0 +1,6 @@
+package frontend
+
+import "embed"
+
+//go:embed dist
+var DistFS embed.FS

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.1.0","build_id":2,"total_build":19}
+{"version":"1.1.0","build_id":3,"total_build":20}

+ 3 - 2
server/go.mod → go.mod

@@ -1,13 +1,14 @@
-module github.com/0xJacky/Nginx-UI/server
+module github.com/0xJacky/Nginx-UI
 
 go 1.16
 
 require (
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/dustin/go-humanize v1.0.0
+	github.com/gin-contrib/static v0.0.1
 	github.com/gin-gonic/gin v1.7.4
 	github.com/go-acme/lego/v4 v4.4.0
-	github.com/go-playground/locales v0.13.0 // indirect
+	github.com/go-playground/locales v0.13.0
 	github.com/go-playground/universal-translator v0.17.0
 	github.com/go-playground/validator/v10 v10.4.1
 	github.com/google/uuid v1.1.1

+ 4 - 0
server/go.sum → go.sum

@@ -107,6 +107,9 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
+github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
+github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
 github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
 github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
 github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
@@ -128,6 +131,7 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
 github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=

+ 32 - 0
main.go

@@ -0,0 +1,32 @@
+package main
+
+import (
+	"flag"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/router"
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/0xJacky/Nginx-UI/tool"
+	"log"
+)
+
+func main() {
+	var dataDir string
+	flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
+	flag.Parse()
+
+	settings.Init(dataDir)
+	model.Init()
+
+	r := router.InitRouter()
+
+	log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
+
+	go tool.AutoCert()
+
+	err := r.Run(":" + settings.ServerSettings.HttpPort)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+}

+ 63 - 0
model/auth.go

@@ -0,0 +1,63 @@
+package model
+
+import (
+	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/dgrijalva/jwt-go"
+	"time"
+)
+
+type Auth struct {
+	Model
+
+	Name     string `json:"name"`
+	Password string `json:"-"`
+}
+
+type AuthToken struct {
+	Token string `json:"token"`
+}
+
+type JWTClaims struct {
+	Name string `json:"name"`
+	jwt.StandardClaims
+}
+
+func GetUser(name string) (user Auth, err error) {
+	err = db.Where("name = ?", name).First(&user).Error
+	if err != nil {
+		return Auth{}, err
+	}
+	return user, err
+}
+
+func DeleteToken(token string) error {
+	return db.Where("token = ?", token).Delete(&AuthToken{}).Error
+}
+
+func CheckToken(token string) int64 {
+	return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
+}
+
+func GenerateJWT(name string) (string, error) {
+	claims := JWTClaims{
+		Name: name,
+		StandardClaims: jwt.StandardClaims{
+			ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
+		},
+	}
+	unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+	signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
+	if err != nil {
+		return "", err
+	}
+
+	err = db.Create(&AuthToken{
+		Token: signedToken,
+	}).Error
+
+	if err != nil {
+		return "", err
+	}
+
+	return signedToken, err
+}

+ 0 - 0
server/model/cert.go → model/cert.go


+ 0 - 0
server/model/config-backup.go → model/config-backup.go


+ 0 - 0
server/model/curd.go → model/curd.go


+ 0 - 0
server/model/log.go → model/log.go


+ 1 - 1
server/model/models.go → model/models.go

@@ -1,7 +1,7 @@
 package model
 
 import (
-	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/0xJacky/Nginx-UI/settings"
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
 	"gorm.io/gorm/logger"

+ 134 - 0
router/routers.go

@@ -0,0 +1,134 @@
+package router
+
+import (
+	"bufio"
+	"encoding/base64"
+	"github.com/0xJacky/Nginx-UI/api"
+	"github.com/0xJacky/Nginx-UI/frontend"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/gin-contrib/static"
+	"github.com/gin-gonic/gin"
+	"io/fs"
+	"log"
+	"net/http"
+	"path/filepath"
+	"strings"
+)
+
+func authRequired() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		token := c.GetHeader("Authorization")
+		if token == "" {
+			tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
+			token = string(tmp)
+			if token == "" {
+				c.JSON(http.StatusForbidden, gin.H{
+					"message": "auth fail",
+				})
+				c.Abort()
+				return
+			}
+		}
+
+		n := model.CheckToken(token)
+
+		if n < 1 {
+			c.JSON(http.StatusForbidden, gin.H{
+				"message": "auth fail",
+			})
+			c.Abort()
+			return
+		}
+		c.Next()
+	}
+}
+
+type serverFileSystemType struct {
+	http.FileSystem
+}
+
+func (f serverFileSystemType) Exists(prefix string, path string) bool {
+	_, err := f.Open(filepath.Join(prefix, path))
+	return err == nil
+}
+
+func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
+
+	sub, err := fs.Sub(frontend.DistFS, filepath.Join("public", dir))
+
+	if err != nil {
+		log.Println(err)
+	}
+
+	serverFileSystem = serverFileSystemType{
+		http.FS(sub),
+	}
+
+	return
+}
+
+func InitRouter() *gin.Engine {
+	r := gin.New()
+	r.Use(gin.Logger())
+
+	r.Use(gin.Recovery())
+
+	r.Use(static.Serve("/", mustFS("")))
+
+	r.NoRoute(func(c *gin.Context) {
+		accept := c.Request.Header.Get("Accept")
+		if strings.Contains(accept, "text/html") {
+			file, _ := mustFS("").Open("index.html")
+			stat, _ := file.Stat()
+			c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
+				bufio.NewReader(file), nil)
+		}
+	})
+
+	g := r.Group("/api")
+	{
+		g.GET("install", api.InstallLockCheck)
+		g.POST("install", api.InstallNginxUI)
+
+		g.POST("/login", api.Login)
+		g.DELETE("/logout", api.Logout)
+
+		g := g.Group("/", authRequired())
+		{
+			g.GET("/analytic", api.Analytic)
+
+			g.GET("/users", api.GetUsers)
+			g.GET("/user/:id", api.GetUser)
+			g.POST("/user", api.AddUser)
+			g.POST("/user/:id", api.EditUser)
+			g.DELETE("/user/:id", api.DeleteUser)
+
+			g.GET("domains", api.GetDomains)
+			g.GET("domain/:name", api.GetDomain)
+			g.POST("domain/:name", api.EditDomain)
+			g.POST("domain/:name/enable", api.EnableDomain)
+			g.POST("domain/:name/disable", api.DisableDomain)
+			g.DELETE("domain/:name", api.DeleteDomain)
+
+			g.GET("configs", api.GetConfigs)
+			g.GET("config/:name", api.GetConfig)
+			g.POST("config", api.AddConfig)
+			g.POST("config/:name", api.EditConfig)
+
+			g.GET("backups", api.GetFileBackupList)
+			g.GET("backup/:id", api.GetFileBackup)
+
+			g.GET("template/:name", api.GetTemplate)
+
+			g.GET("cert/issue/:domain", api.IssueCert)
+			g.GET("cert/:domain/info", api.CertInfo)
+
+			// 添加域名到自动续期列表
+			g.POST("cert/:domain", api.AddDomainToAutoCert)
+			// 从自动续期列表中删除域名
+			g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
+		}
+	}
+
+	return r
+}

+ 0 - 57
server/api/auth.go

@@ -1,57 +0,0 @@
-package api
-
-import (
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/gin-gonic/gin"
-    "golang.org/x/crypto/bcrypt"
-    "net/http"
-)
-
-type LoginUser struct {
-    Name     string `json:"name" binding:"required,max=255"`
-    Password string `json:"password" binding:"required,max=255"`
-}
-
-func Login(c *gin.Context) {
-    var user LoginUser
-    ok := BindAndValid(c, &user)
-    if !ok {
-        return
-    }
-
-    u, _ := model.GetUser(user.Name)
-
-    if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
-        c.JSON(http.StatusForbidden, gin.H{
-            "message": "用户名或密码错误",
-        })
-        return
-    }
-
-    token, err := model.GenerateJWT(u.Name)
-    if err != nil {
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": err.Error(),
-        })
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-        "token":   token,
-    })
-}
-
-func Logout(c *gin.Context) {
-    token := c.GetHeader("Authorization")
-    if token != "" {
-        err := model.DeleteToken(token)
-        if err != nil {
-            c.JSON(http.StatusInternalServerError, gin.H{
-                "message": err.Error(),
-            })
-            return
-        }
-    }
-    c.JSON(http.StatusNoContent, nil)
-}

+ 0 - 263
server/api/domain.go

@@ -1,263 +0,0 @@
-package api
-
-import (
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/tool"
-    "github.com/gin-gonic/gin"
-    "io/ioutil"
-    "net/http"
-    "os"
-    "path/filepath"
-)
-
-func GetDomains(c *gin.Context) {
-    orderBy := c.Query("order_by")
-    sort := c.DefaultQuery("sort", "desc")
-
-    mySort := map[string]string{
-        "enabled": "bool",
-        "name":    "string",
-        "modify":  "time",
-    }
-
-    configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
-
-    enabledConfigMap := make(map[string]bool)
-    for i := range enabledConfig {
-        enabledConfigMap[enabledConfig[i].Name()] = true
-    }
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    var configs []gin.H
-
-    for i := range configFiles {
-        file := configFiles[i]
-        if !file.IsDir() {
-            configs = append(configs, gin.H{
-                "name":    file.Name(),
-                "size":    file.Size(),
-                "modify":  file.ModTime(),
-                "enabled": enabledConfigMap[file.Name()],
-            })
-        }
-    }
-
-    configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
-
-    c.JSON(http.StatusOK, gin.H{
-        "configs": configs,
-    })
-}
-
-func GetDomain(c *gin.Context) {
-    name := c.Param("name")
-    path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
-
-    enabled := true
-    if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
-        enabled = false
-    }
-
-    content, err := ioutil.ReadFile(path)
-
-    if err != nil {
-        if os.IsNotExist(err) {
-            c.JSON(http.StatusNotFound, gin.H{
-                "message": err.Error(),
-            })
-            return
-        }
-        ErrHandler(c, err)
-        return
-    }
-
-    _, err = model.FirstCert(name)
-
-    c.JSON(http.StatusOK, gin.H{
-        "enabled":   enabled,
-        "name":      name,
-        "config":    string(content),
-        "auto_cert": err == nil,
-    })
-
-}
-
-func EditDomain(c *gin.Context) {
-    var err error
-    name := c.Param("name")
-    request := make(gin.H)
-    err = c.BindJSON(&request)
-    path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
-
-    err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
-    if _, err = os.Stat(enabledConfigFilePath); err == nil {
-        // 测试配置文件
-        err = tool.TestNginxConf(enabledConfigFilePath)
-        if err != nil {
-            c.JSON(http.StatusInternalServerError, gin.H{
-                "message": err.Error(),
-            })
-            return
-        }
-
-        output := tool.ReloadNginx()
-
-        if output != "" {
-            c.JSON(http.StatusInternalServerError, gin.H{
-                "message": output,
-            })
-            return
-        }
-    }
-
-    GetDomain(c)
-}
-
-func EnableDomain(c *gin.Context) {
-    configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
-    enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
-
-    _, err := os.Stat(configFilePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.Symlink(configFilePath, enabledConfigFilePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    // 测试配置文件,不通过则撤回启用
-    err = tool.TestNginxConf(enabledConfigFilePath)
-    if err != nil {
-        _ = os.Remove(enabledConfigFilePath)
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": err.Error(),
-        })
-        return
-    }
-
-    output := tool.ReloadNginx()
-
-    if output != "" {
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": output,
-        })
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
-}
-
-func DisableDomain(c *gin.Context) {
-    enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
-
-    _, err := os.Stat(enabledConfigFilePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    err = os.Remove(enabledConfigFilePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    output := tool.ReloadNginx()
-
-    if output != "" {
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": output,
-        })
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
-}
-
-func DeleteDomain(c *gin.Context) {
-    var err error
-    name := c.Param("name")
-    availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
-    enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
-
-    if _, err = os.Stat(availablePath); os.IsNotExist(err) {
-        c.JSON(http.StatusNotFound, gin.H{
-            "message": "site not found",
-        })
-        return
-    }
-
-    if _, err = os.Stat(enabledPath); err == nil {
-        c.JSON(http.StatusNotAcceptable, gin.H{
-            "message": "site is enabled",
-        })
-        return
-    }
-
-    cert := model.Cert{Domain: name}
-    _ = cert.Remove()
-
-    err = os.Remove(availablePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
-
-}
-
-func AddDomainToAutoCert(c *gin.Context) {
-    domain := c.Param("domain")
-    cert, err := model.FirstOrCreateCert(domain)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    c.JSON(http.StatusOK, cert)
-}
-
-func RemoveDomainFromAutoCert(c *gin.Context) {
-    cert := model.Cert{
-        Domain: c.Param("domain"),
-    }
-    err := cert.Remove()
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    c.JSON(http.StatusOK, nil)
-}

+ 0 - 37
server/api/template.go

@@ -1,37 +0,0 @@
-package api
-
-import (
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/gin-gonic/gin"
-    "io/ioutil"
-    "net/http"
-    "os"
-    "path/filepath"
-    "strings"
-)
-
-func GetTemplate(c *gin.Context) {
-    name := c.Param("name")
-    path := filepath.Join("template", name)
-    content, err := ioutil.ReadFile(path)
-
-    _content := string(content)
-    _content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
-        settings.ServerSettings.HTTPChallengePort)
-
-    if err != nil {
-        if os.IsNotExist(err) {
-            c.JSON(http.StatusNotFound, gin.H{
-                "message": err.Error(),
-            })
-            return
-        }
-        ErrHandler(c, err)
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message":  "ok",
-        "template": _content,
-    })
-}

+ 0 - 32
server/main.go

@@ -1,32 +0,0 @@
-package main
-
-import (
-    "flag"
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/router"
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/0xJacky/Nginx-UI/server/tool"
-    "log"
-)
-
-func main() {
-    var dataDir string
-    flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
-    flag.Parse()
-
-    settings.Init(dataDir)
-    model.Init()
-
-    r := router.InitRouter()
-
-    log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
-
-    go tool.AutoCert()
-
-    err := r.Run(":" + settings.ServerSettings.HttpPort)
-
-    if err != nil {
-        log.Fatal(err)
-    }
-
-}

+ 0 - 63
server/model/auth.go

@@ -1,63 +0,0 @@
-package model
-
-import (
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/dgrijalva/jwt-go"
-    "time"
-)
-
-type Auth struct {
-    Model
-
-    Name     string `json:"name"`
-    Password string `json:"-"`
-}
-
-type AuthToken struct {
-    Token   string `json:"token"`
-}
-
-type JWTClaims struct {
-    Name     string `json:"name"`
-    jwt.StandardClaims
-}
-
-func GetUser(name string) (user Auth, err error){
-    err = db.Where("name = ?", name).First(&user).Error
-    if err != nil {
-        return Auth{}, err
-    }
-    return user, err
-}
-
-func DeleteToken(token string) error {
-    return db.Where("token = ?", token).Delete(&AuthToken{}).Error
-}
-
-func CheckToken(token string) int64 {
-    return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
-}
-
-func GenerateJWT(name string) (string, error) {
-    claims := JWTClaims{
-        Name: name,
-        StandardClaims: jwt.StandardClaims{
-            ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
-        },
-    }
-    unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
-    signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
-    if err != nil {
-        return "", err
-    }
-
-    err = db.Create(&AuthToken{
-        Token: signedToken,
-    }).Error
-
-    if err != nil {
-        return "", err
-    }
-
-    return signedToken, err
-}

+ 0 - 94
server/router/routers.go

@@ -1,94 +0,0 @@
-package router
-
-import (
-	"encoding/base64"
-	"github.com/0xJacky/Nginx-UI/server/api"
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/gin-gonic/gin"
-	"net/http"
-)
-
-func authRequired() gin.HandlerFunc {
-	return func(c *gin.Context) {
-		token := c.GetHeader("Authorization")
-		if token == "" {
-			tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
-			token = string(tmp)
-			if token == "" {
-				c.JSON(http.StatusForbidden, gin.H{
-					"message": "auth fail",
-				})
-				c.Abort()
-				return
-			}
-		}
-
-		n := model.CheckToken(token)
-
-		if n < 1 {
-			c.JSON(http.StatusForbidden, gin.H{
-				"message": "auth fail",
-			})
-			c.Abort()
-			return
-		}
-		c.Next()
-	}
-}
-
-func InitRouter() *gin.Engine {
-	r := gin.New()
-	r.Use(gin.Logger())
-
-	r.Use(gin.Recovery())
-
-	r.GET("/", func(c *gin.Context) {
-		c.JSON(http.StatusOK, gin.H{
-			"message": "Hello World",
-		})
-	})
-
-	r.GET("install", api.InstallLockCheck)
-	r.POST("install", api.InstallNginxUI)
-
-	r.POST("/login", api.Login)
-	r.DELETE("/logout", api.Logout)
-
-	g := r.Group("/", authRequired())
-	{
-		r.GET("/analytic", api.Analytic)
-
-		g.GET("/users", api.GetUsers)
-		g.GET("/user/:id", api.GetUser)
-		g.POST("/user", api.AddUser)
-		g.POST("/user/:id", api.EditUser)
-		g.DELETE("/user/:id", api.DeleteUser)
-
-		g.GET("domains", api.GetDomains)
-		g.GET("domain/:name", api.GetDomain)
-		g.POST("domain/:name", api.EditDomain)
-		g.POST("domain/:name/enable", api.EnableDomain)
-		g.POST("domain/:name/disable", api.DisableDomain)
-		g.DELETE("domain/:name", api.DeleteDomain)
-
-		g.GET("configs", api.GetConfigs)
-		g.GET("config/:name", api.GetConfig)
-		g.POST("config", api.AddConfig)
-		g.POST("config/:name", api.EditConfig)
-
-		g.GET("backups", api.GetFileBackupList)
-		g.GET("backup/:id", api.GetFileBackup)
-
-		g.GET("template/:name", api.GetTemplate)
-
-		g.GET("cert/issue/:domain", api.IssueCert)
-		g.GET("cert/:domain/info", api.CertInfo)
-
-		// 添加域名到自动续期列表
-		g.POST("cert/:domain", api.AddDomainToAutoCert)
-		// 从自动续期列表中删除域名
-		g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
-	}
-
-	return r
-}

+ 0 - 59
server/test/acme_test.go

@@ -1,59 +0,0 @@
-package test
-
-import (
-    "fmt"
-    "github.com/0xJacky/Nginx-UI/server/tool"
-    "io/ioutil"
-    "log"
-    "os"
-    "os/exec"
-    "strings"
-    "testing"
-)
-
-func TestAcme(t *testing.T) {
-    const acmePath = "/usr/local/acme.sh"
-    _, err := os.Stat(acmePath)
-    log.Println("[found] acme.sh ", acmePath)
-    if err != nil {
-        log.Println(err)
-        if os.IsNotExist(err) {
-            log.Println("[not found] acme.sh, installing...")
-
-            if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
-                _ = os.Mkdir("../tmp", 0644)
-            }
-
-            out, err := exec.Command("curl",  "-o", "../tmp/acme.sh", "https://get.acme.sh").
-                CombinedOutput()
-            if err != nil {
-                log.Println(err)
-                return
-            }
-            fmt.Printf("%s\n", out)
-
-            log.Println("[acme.sh] downloaded")
-
-            file, _ := ioutil.ReadFile("../tmp/acme.sh")
-
-            fileString := string(file)
-            fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
-                "https://ghproxy.com/https://raw.githubusercontent.com", -1)
-
-            _ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
-
-            out, err = exec.Command("bash",  "../tmp/acme.sh",
-                "install",
-                "--log",
-                "--home", "/usr/local/acme.sh",
-                "--cert-home", tool.GetNginxConfPath("ssl")).
-                CombinedOutput()
-            if err != nil {
-                log.Println(err)
-                return
-            }
-            fmt.Printf("%s\n", out)
-
-        }
-    }
-}

+ 0 - 40
server/test/cert_test.go

@@ -1,40 +0,0 @@
-package test
-
-import (
-    "fmt"
-    "github.com/0xJacky/Nginx-UI/server/tool"
-    "log"
-    "os"
-    "os/exec"
-    "testing"
-)
-
-func TestCert(t *testing.T)  {
-    out, err := exec.Command("bash",  "/usr/local/acme.sh/acme.sh",
-        "--issue",
-        "-d", "test.ojbk.me",
-        "--nginx").CombinedOutput()
-    if err != nil {
-        log.Println(err)
-        return
-    }
-    fmt.Printf("%s\n", out)
-
-    _, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
-
-    if err != nil {
-        log.Println(err)
-        return
-    }
-    log.Println("[found]", "fullchain.cer")
-    _, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
-
-    if err != nil {
-        log.Println(err)
-        return
-    }
-
-    log.Println("[found]", "cert key")
-
-    log.Println("申请成功")
-}

+ 0 - 169
server/tool/cert.go

@@ -1,169 +0,0 @@
-package tool
-
-import (
-	"crypto"
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	"crypto/rand"
-	"crypto/tls"
-	"crypto/x509"
-	"github.com/0xJacky/Nginx-UI/server/model"
-	"github.com/0xJacky/Nginx-UI/server/settings"
-	"github.com/go-acme/lego/v4/certcrypto"
-	"github.com/go-acme/lego/v4/certificate"
-	"github.com/go-acme/lego/v4/challenge/http01"
-	"github.com/go-acme/lego/v4/lego"
-	"github.com/go-acme/lego/v4/registration"
-	"io"
-	"io/ioutil"
-	"log"
-	"net/http"
-	"os"
-	"path/filepath"
-	"time"
-)
-
-// MyUser You'll need a user or account type that implements acme.User
-type MyUser struct {
-	Email        string
-	Registration *registration.Resource
-	key          crypto.PrivateKey
-}
-
-func (u *MyUser) GetEmail() string {
-	return u.Email
-}
-func (u MyUser) GetRegistration() *registration.Resource {
-	return u.Registration
-}
-func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
-	return u.key
-}
-
-func AutoCert() {
-	for {
-		log.Println("[AutoCert] Start")
-		autoCertList := model.GetAutoCertList()
-		for i := range autoCertList {
-			domain := autoCertList[i].Domain
-			key := GetCertInfo(domain)
-			// 未到一个月
-			if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
-				continue
-			}
-			// 过一个月了
-			err := IssueCert(domain)
-			if err != nil {
-				log.Println(err)
-			}
-		}
-		time.Sleep(1 * time.Hour)
-	}
-}
-
-func GetCertInfo(domain string) (key *x509.Certificate) {
-	ts := &http.Transport{
-		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
-	}
-
-	client := &http.Client{Transport: ts}
-
-	response, err := client.Get("https://" + domain)
-
-	if err != nil {
-		return
-	}
-
-	defer func(Body io.ReadCloser) {
-		err = Body.Close()
-		if err != nil {
-			log.Println(err)
-			return
-		}
-	}(response.Body)
-
-	key = response.TLS.PeerCertificates[0]
-
-	return
-}
-
-func IssueCert(domain string) error {
-	// Create a user. New accounts need an email and private key to start.
-	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-
-	myUser := MyUser{
-		Email: settings.ServerSettings.Email,
-		key:   privateKey,
-	}
-
-	config := lego.NewConfig(&myUser)
-
-	//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
-	config.Certificate.KeyType = certcrypto.RSA2048
-
-	// A client facilitates communication with the CA server.
-	client, err := lego.NewClient(config)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-
-	err = client.Challenge.SetHTTP01Provider(
-		http01.NewProviderServer("",
-			settings.ServerSettings.HTTPChallengePort,
-		),
-	)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-
-	// New users will need to register
-	reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-	myUser.Registration = reg
-
-	request := certificate.ObtainRequest{
-		Domains: []string{domain},
-		Bundle:  true,
-	}
-	certificates, err := client.Certificate.Obtain(request)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-	saveDir := GetNginxConfPath("ssl/" + domain)
-	if _, err := os.Stat(saveDir); os.IsNotExist(err) {
-		err = os.Mkdir(saveDir, 0755)
-		if err != nil {
-			log.Println("fail to create", saveDir)
-			return err
-		}
-	}
-
-	// Each certificate comes back with the cert bytes, the bytes of the client's
-	// private key, and a certificate URL. SAVE THESE TO DISK.
-	err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
-		certificates.Certificate, 0644)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-	err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
-		certificates.PrivateKey, 0644)
-	if err != nil {
-		log.Println(err)
-		return err
-	}
-
-	ReloadNginx()
-
-	return nil
-}

+ 0 - 0
server/settings/settings.go → settings/settings.go


+ 0 - 0
server/template/http-conf → template/http-conf


+ 0 - 0
server/template/https-conf → template/https-conf


+ 59 - 0
test/acme_test.go

@@ -0,0 +1,59 @@
+package test
+
+import (
+	"fmt"
+	"github.com/0xJacky/Nginx-UI/tool"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+	"testing"
+)
+
+func TestAcme(t *testing.T) {
+	const acmePath = "/usr/local/acme.sh"
+	_, err := os.Stat(acmePath)
+	log.Println("[found] acme.sh ", acmePath)
+	if err != nil {
+		log.Println(err)
+		if os.IsNotExist(err) {
+			log.Println("[not found] acme.sh, installing...")
+
+			if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
+				_ = os.Mkdir("../tmp", 0644)
+			}
+
+			out, err := exec.Command("curl", "-o", "../tmp/acme.sh", "https://get.acme.sh").
+				CombinedOutput()
+			if err != nil {
+				log.Println(err)
+				return
+			}
+			fmt.Printf("%s\n", out)
+
+			log.Println("[acme.sh] downloaded")
+
+			file, _ := ioutil.ReadFile("../tmp/acme.sh")
+
+			fileString := string(file)
+			fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
+				"https://ghproxy.com/https://raw.githubusercontent.com", -1)
+
+			_ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
+
+			out, err = exec.Command("bash", "../tmp/acme.sh",
+				"install",
+				"--log",
+				"--home", "/usr/local/acme.sh",
+				"--cert-home", tool.GetNginxConfPath("ssl")).
+				CombinedOutput()
+			if err != nil {
+				log.Println(err)
+				return
+			}
+			fmt.Printf("%s\n", out)
+
+		}
+	}
+}

+ 0 - 0
server/test/analytic_test.go → test/analytic_test.go


+ 40 - 0
test/cert_test.go

@@ -0,0 +1,40 @@
+package test
+
+import (
+	"fmt"
+	"github.com/0xJacky/Nginx-UI/tool"
+	"log"
+	"os"
+	"os/exec"
+	"testing"
+)
+
+func TestCert(t *testing.T) {
+	out, err := exec.Command("bash", "/usr/local/acme.sh/acme.sh",
+		"--issue",
+		"-d", "test.ojbk.me",
+		"--nginx").CombinedOutput()
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	fmt.Printf("%s\n", out)
+
+	_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
+
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	log.Println("[found]", "fullchain.cer")
+	_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
+
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	log.Println("[found]", "cert key")
+
+	log.Println("申请成功")
+}

+ 0 - 0
server/test/lego_test.go → test/lego_test.go


+ 0 - 0
server/test/nginx_test.go → test/nginx_test.go


+ 169 - 0
tool/cert.go

@@ -0,0 +1,169 @@
+package tool
+
+import (
+    "crypto"
+    "crypto/ecdsa"
+    "crypto/elliptic"
+    "crypto/rand"
+    "crypto/tls"
+    "crypto/x509"
+    "github.com/0xJacky/Nginx-UI/model"
+    "github.com/0xJacky/Nginx-UI/settings"
+    "github.com/go-acme/lego/v4/certcrypto"
+    "github.com/go-acme/lego/v4/certificate"
+    "github.com/go-acme/lego/v4/challenge/http01"
+    "github.com/go-acme/lego/v4/lego"
+    "github.com/go-acme/lego/v4/registration"
+    "io"
+    "io/ioutil"
+    "log"
+    "net/http"
+    "os"
+    "path/filepath"
+    "time"
+)
+
+// MyUser You'll need a user or account type that implements acme.User
+type MyUser struct {
+    Email        string
+    Registration *registration.Resource
+    key          crypto.PrivateKey
+}
+
+func (u *MyUser) GetEmail() string {
+    return u.Email
+}
+func (u MyUser) GetRegistration() *registration.Resource {
+    return u.Registration
+}
+func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
+    return u.key
+}
+
+func AutoCert() {
+    for {
+        log.Println("[AutoCert] Start")
+        autoCertList := model.GetAutoCertList()
+        for i := range autoCertList {
+            domain := autoCertList[i].Domain
+            key := GetCertInfo(domain)
+            // 未到一个月
+            if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
+                continue
+            }
+            // 过一个月了
+            err := IssueCert(domain)
+            if err != nil {
+                log.Println(err)
+            }
+        }
+        time.Sleep(1 * time.Hour)
+    }
+}
+
+func GetCertInfo(domain string) (key *x509.Certificate) {
+    ts := &http.Transport{
+        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+    }
+
+    client := &http.Client{Transport: ts}
+
+    response, err := client.Get("https://" + domain)
+
+    if err != nil {
+        return
+    }
+
+    defer func(Body io.ReadCloser) {
+        err = Body.Close()
+        if err != nil {
+            log.Println(err)
+            return
+        }
+    }(response.Body)
+
+    key = response.TLS.PeerCertificates[0]
+
+    return
+}
+
+func IssueCert(domain string) error {
+    // Create a user. New accounts need an email and private key to start.
+    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+
+    myUser := MyUser{
+        Email: settings.ServerSettings.Email,
+        key:   privateKey,
+    }
+
+    config := lego.NewConfig(&myUser)
+
+    //config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
+    config.Certificate.KeyType = certcrypto.RSA2048
+
+    // A client facilitates communication with the CA server.
+    client, err := lego.NewClient(config)
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+
+    err = client.Challenge.SetHTTP01Provider(
+        http01.NewProviderServer("",
+            settings.ServerSettings.HTTPChallengePort,
+        ),
+    )
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+
+    // New users will need to register
+    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+    myUser.Registration = reg
+
+    request := certificate.ObtainRequest{
+        Domains: []string{domain},
+        Bundle:  true,
+    }
+    certificates, err := client.Certificate.Obtain(request)
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+    saveDir := GetNginxConfPath("ssl/" + domain)
+    if _, err := os.Stat(saveDir); os.IsNotExist(err) {
+        err = os.Mkdir(saveDir, 0755)
+        if err != nil {
+            log.Println("fail to create", saveDir)
+            return err
+        }
+    }
+
+    // Each certificate comes back with the cert bytes, the bytes of the client's
+    // private key, and a certificate URL. SAVE THESE TO DISK.
+    err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
+        certificates.Certificate, 0644)
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+    err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
+        certificates.PrivateKey, 0644)
+    if err != nil {
+        log.Println(err)
+        return err
+    }
+
+    ReloadNginx()
+
+    return nil
+}

+ 0 - 0
server/tool/config_list.go → tool/config_list.go


+ 0 - 0
server/tool/nginx.go → tool/nginx.go


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно