Prechádzať zdrojové kódy

Added build nginx config from json

0xJacky 3 rokov pred
rodič
commit
32153a9b00

+ 152 - 157
server/api/cert.go

@@ -1,169 +1,164 @@
 package api
 
 import (
-    "encoding/json"
-    "github.com/0xJacky/Nginx-UI/server/settings"
-    "github.com/0xJacky/Nginx-UI/server/tool"
-    "github.com/0xJacky/Nginx-UI/server/tool/nginx"
-    "github.com/gin-gonic/gin"
-    "github.com/gorilla/websocket"
-    "log"
-    "net/http"
-    "os"
+	"encoding/json"
+	"github.com/0xJacky/Nginx-UI/server/settings"
+	"github.com/0xJacky/Nginx-UI/server/tool"
+	"github.com/0xJacky/Nginx-UI/server/tool/nginx"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"log"
+	"net/http"
+	"os"
 )
 
 func CertInfo(c *gin.Context) {
-    domain := c.Param("domain")
+	domain := c.Param("domain")
 
-    key, err := tool.GetCertInfo(domain)
+	key, err := tool.GetCertInfo(domain)
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "subject_name": key.Subject.CommonName,
-        "issuer_name":  key.Issuer.CommonName,
-        "not_after":    key.NotAfter,
-        "not_before":   key.NotBefore,
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"error":        err,
+		"subject_name": key.Subject.CommonName,
+		"issuer_name":  key.Issuer.CommonName,
+		"not_after":    key.NotAfter,
+		"not_before":   key.NotBefore,
+	})
 }
 
 func IssueCert(c *gin.Context) {
-    domain := c.Param("domain")
-    var upGrader = websocket.Upgrader{
-        CheckOrigin: func(r *http.Request) bool {
-            return true
-        },
-    }
-
-    // upgrade http to websocket
-    ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
-    if err != nil {
-        log.Println(err)
-        return
-    }
-
-    defer func(ws *websocket.Conn) {
-        err := ws.Close()
-        if err != nil {
-            log.Println(err)
-            return
-        }
-    }(ws)
-
-    for {
-        // read
-        mt, message, err := ws.ReadMessage()
-        if err != nil {
-            break
-        }
-        if string(message) == "go" {
-            var m []byte
-
-            if settings.ServerSettings.Demo {
-                m, _ = json.Marshal(gin.H{
-                    "status":  "error",
-                    "message": "this feature is not available in demo",
-                })
-                _ = ws.WriteMessage(mt, m)
-                return
-            }
-
-            err = tool.IssueCert(domain)
-
-            if err != nil {
-
-                log.Println(err)
-
-                m, err = json.Marshal(gin.H{
-                    "status":  "error",
-                    "message": err.Error(),
-                })
-
-                if err != nil {
-                    log.Println(err)
-                    return
-                }
-
-                err = ws.WriteMessage(mt, m)
-
-                if err != nil {
-                    log.Println(err)
-                    return
-                }
-
-                return
-            }
-
-            sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
-            _, err = os.Stat(sslCertificatePath)
-
-            if err != nil {
-                log.Println(err)
-                return
-            }
-
-            log.Println("[found]", "fullchain.cer")
-            m, err = json.Marshal(gin.H{
-                "status":  "success",
-                "message": "[found] fullchain.cer",
-            })
-
-            if err != nil {
-                log.Println(err)
-                return
-            }
-
-            err = ws.WriteMessage(mt, m)
-
-            if err != nil {
-                log.Println(err)
-                return
-            }
-
-            sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
-            _, err = os.Stat(sslCertificateKeyPath)
-
-            if err != nil {
-                log.Println(err)
-                return
-            }
-
-            log.Println("[found]", "cert key")
-            m, err = json.Marshal(gin.H{
-                "status":  "success",
-                "message": "[found] cert key",
-            })
-
-            if err != nil {
-                log.Println(err)
-            }
-
-            err = ws.WriteMessage(mt, m)
-
-            if err != nil {
-                log.Println(err)
-            }
-
-            log.Println("申请成功")
-            m, err = json.Marshal(gin.H{
-                "status":              "success",
-                "message":             "申请成功",
-                "ssl_certificate":     sslCertificatePath,
-                "ssl_certificate_key": sslCertificateKeyPath,
-            })
-
-            if err != nil {
-                log.Println(err)
-            }
-
-            err = ws.WriteMessage(mt, m)
-
-            if err != nil {
-                log.Println(err)
-            }
-        }
-    }
+	domain := c.Param("domain")
+	var upGrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+
+	// upgrade http to websocket
+	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	defer func(ws *websocket.Conn) {
+		err := ws.Close()
+		if err != nil {
+			log.Println("defer websocket close err", err)
+		}
+	}(ws)
+
+	for {
+		// read
+		mt, message, err := ws.ReadMessage()
+		if err != nil {
+			break
+		}
+		if string(message) == "go" {
+			var m []byte
+
+			if settings.ServerSettings.Demo {
+				m, _ = json.Marshal(gin.H{
+					"status":  "error",
+					"message": "this feature is not available in demo",
+				})
+				_ = ws.WriteMessage(mt, m)
+				return
+			}
+
+			err = tool.IssueCert(domain)
+
+			if err != nil {
+
+				log.Println(err)
+
+				m, err = json.Marshal(gin.H{
+					"status":  "error",
+					"message": err.Error(),
+				})
+
+				if err != nil {
+					log.Println(err)
+					return
+				}
+
+				err = ws.WriteMessage(mt, m)
+
+				if err != nil {
+					log.Println(err)
+					return
+				}
+
+				return
+			}
+
+			sslCertificatePath := nginx.GetNginxConfPath("ssl/" + domain + "/fullchain.cer")
+			_, err = os.Stat(sslCertificatePath)
+
+			if err != nil {
+				log.Println(err)
+				return
+			}
+
+			log.Println("[found]", "fullchain.cer")
+			m, err = json.Marshal(gin.H{
+				"status":  "success",
+				"message": "[found] fullchain.cer",
+			})
+
+			if err != nil {
+				log.Println(err)
+				return
+			}
+
+			err = ws.WriteMessage(mt, m)
+
+			if err != nil {
+				log.Println(err)
+				return
+			}
+
+			sslCertificateKeyPath := nginx.GetNginxConfPath("ssl/" + domain + "/" + domain + ".key")
+			_, err = os.Stat(sslCertificateKeyPath)
+
+			if err != nil {
+				log.Println(err)
+				return
+			}
+
+			log.Println("[found]", "cert key")
+			m, err = json.Marshal(gin.H{
+				"status":  "success",
+				"message": "[found] cert key",
+			})
+
+			if err != nil {
+				log.Println(err)
+			}
+
+			err = ws.WriteMessage(mt, m)
+
+			if err != nil {
+				log.Println(err)
+			}
+
+			log.Println("申请成功")
+			m, err = json.Marshal(gin.H{
+				"status":              "success",
+				"message":             "申请成功",
+				"ssl_certificate":     sslCertificatePath,
+				"ssl_certificate_key": sslCertificateKeyPath,
+			})
+
+			if err != nil {
+				log.Println(err)
+			}
+
+			err = ws.WriteMessage(mt, m)
+
+			if err != nil {
+				log.Println(err)
+			}
+		}
+	}
 }

+ 4 - 10
server/api/domain.go

@@ -71,15 +71,9 @@ func GetDomain(c *gin.Context) {
 		enabled = false
 	}
 
-	content, err := ioutil.ReadFile(path)
+	config, err := nginx.ParseNgxConfig(path)
 
 	if err != nil {
-		if os.IsNotExist(err) {
-			c.JSON(http.StatusNotFound, gin.H{
-				"message": err.Error(),
-			})
-			return
-		}
 		ErrHandler(c, err)
 		return
 	}
@@ -89,8 +83,8 @@ func GetDomain(c *gin.Context) {
 	c.JSON(http.StatusOK, gin.H{
 		"enabled":   enabled,
 		"name":      name,
-		"config":    string(content),
-		"auto_cert": err == nil,
+		"config":    config.BuildConfig(),
+		"tokenized": config,
 	})
 
 }
@@ -150,7 +144,7 @@ func EnableDomain(c *gin.Context) {
 		return
 	}
 
-	// 测试配置文件,不通过则撤回启用
+	// Test nginx config, if not pass then rollback.
 	err = nginx.TestNginxConf()
 	if err != nil {
 		_ = os.Remove(enabledConfigFilePath)

+ 6 - 2
server/test/nextcloud_ngx.conf

@@ -17,6 +17,10 @@ server {
 
     fastcgi_hide_header X-Powered-By;  # Remove X-Powered-By, which is an information leak
 
+    if ($invalid_referer) {
+        return 403;
+    }
+
     location = /robots.txt {
         allow all;
         log_not_found off;
@@ -54,13 +58,13 @@ server {
     fastcgi_buffers 64 4K;
 
     # Enable gzip but do not remove ETag headers
-    gzip on; gzip_vary on; location /x/ {} gzip_comp_level 4;
+    gzip on; gzip_vary on; location /x/ {}gzip_comp_level 4;
     gzip_min_length 256;gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
     gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
 
     # Uncomment if your server is build with the ngx_pagespeed module
     # This module is currently not supported.
-    #pagespeed off;
+    # pagespeed off;
     location / {
         if ( $http_user_agent ~ ^DavClnt ) {
             return 302 /remote.php/webdav/$is_args$args;

+ 83 - 0
server/tool/nginx/build_config.go

@@ -0,0 +1,83 @@
+package nginx
+
+import (
+	"bufio"
+	"fmt"
+	"strings"
+)
+
+func buildComments(orig string, indent int) (content string) {
+	scanner := bufio.NewScanner(strings.NewReader(orig))
+	for scanner.Scan() {
+		content += strings.Repeat("\t", indent) + "# " + scanner.Text() + "\n"
+	}
+	content = strings.TrimLeft(content, "\n")
+	return
+}
+
+func (c *NgxConfig) BuildConfig() (content string) {
+
+	// Custom
+	if c.Custom != "" {
+		content += fmtCode(c.Custom)
+		content += "\n\n"
+	}
+
+	// Upstreams
+	for _, u := range c.Upstreams {
+
+		upstream := ""
+		var comments string
+		for _, directive := range u.Directives {
+			if directive.Comments != "" {
+				comments = buildComments(directive.Comments, 1)
+			}
+			upstream += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig())
+		}
+		comments = buildComments(u.Comments, 1)
+		content += fmt.Sprintf("upstream %s {\n%s%s}\n\n", u.Name, comments, upstream)
+	}
+
+	// Servers
+	for _, s := range c.Servers {
+		server := ""
+
+		// directives
+		for _, directive := range s.Directives {
+			var comments string
+			if directive.Comments != "" {
+				comments = buildComments(directive.Comments, 1)
+			}
+			if directive.Directive == If {
+				server += fmt.Sprintf("%s%s\n", comments, fmtCodeWithIndent(directive.Params, 1))
+			} else {
+				server += fmt.Sprintf("%s\t%s;\n", comments, directive.Orig())
+			}
+		}
+
+		if len(s.Directives) > 0 {
+			server += "\n"
+		}
+
+		// locations
+		locations := ""
+		for _, location := range s.Locations {
+			locationContent := ""
+			scanner := bufio.NewScanner(strings.NewReader(location.Content))
+			for scanner.Scan() {
+				locationContent += "\t\t" + scanner.Text() + "\n"
+			}
+			var comments string
+			if location.Comments != "" {
+				comments = buildComments(location.Comments, 1)
+			}
+			locations += fmt.Sprintf("%s\tlocation %s {\n%s\t}\n\n", comments, location.Path, locationContent)
+		}
+
+		server += locations
+
+		content += fmt.Sprintf("server {\n%s}\n\n", server)
+	}
+
+	return
+}

+ 49 - 0
server/tool/nginx/format_code.go

@@ -0,0 +1,49 @@
+package nginx
+
+import (
+    "bufio"
+    "github.com/emirpasic/gods/stacks/linkedliststack"
+    "strings"
+)
+
+func fmtCode(content string) (fmtContent string) {
+    fmtContent = fmtCodeWithIndent(content, 0)
+    return
+}
+
+func fmtCodeWithIndent(content string, indent int) (fmtContent string) {
+    /*
+       Format content
+       1. TrimSpace for each line
+       2. use stack to count how many \t should add
+    */
+    stack := linkedliststack.New()
+
+    scanner := bufio.NewScanner(strings.NewReader(content))
+
+    for scanner.Scan() {
+        text := scanner.Text()
+        text = strings.TrimSpace(text)
+
+        before := stack.Size()
+
+        for _, char := range text {
+            matchParentheses(stack, char)
+        }
+
+        after := stack.Size()
+
+        fmtContent += strings.Repeat("\t", indent)
+
+        if before == after {
+            fmtContent += strings.Repeat("\t", stack.Size()) + text + "\n"
+        } else {
+            fmtContent += text + "\n"
+        }
+
+    }
+
+    fmtContent = strings.Trim(fmtContent, "\n")
+
+    return
+}

+ 24 - 9
server/tool/nginx/parse.go

@@ -15,6 +15,7 @@ const (
 	Upstream     = "upstream"
 	CommentStart = "#"
 	Empty        = ""
+	If           = "if"
 )
 
 func matchParentheses(stack *linkedliststack.Stack, v int32) {
@@ -44,20 +45,25 @@ func parseDirective(scanner *bufio.Scanner) (d NgxDirective) {
 		return
 	}
 
-	sep := len(text) - 1
-	for k, v := range text {
-		if unicode.IsSpace(v) {
-			sep = k
-			break
+	if len(text) > 1 {
+		sep := len(text) - 1
+		for k, v := range text {
+			if unicode.IsSpace(v) {
+				sep = k
+				break
+			}
 		}
-	}
 
-	d.Directive = text[0:sep]
-	d.Params = text[sep:]
+		d.Directive = text[0:sep]
+		d.Params = text[sep:]
+	} else {
+		d.Directive = text
+		return
+	}
 
 	stack := linkedliststack.New()
 
-	if d.Directive == Server || d.Directive == Upstream || d.Directive == Location {
+	if d.Directive == Server || d.Directive == Upstream || d.Directive == Location || d.Directive == If {
 		// { } in one line
 		// location = /.well-known/carddav { return 301 /remote.php/dav/; }
 		if strings.Contains(d.Params, "{") {
@@ -118,6 +124,10 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
 			c.parseUpstream(paramsScanner)
 		case CommentStart:
 			c.commentQueue.Enqueue(d.Params)
+		case Empty:
+			continue
+		default:
+			c.Custom += d.Orig() + "\n"
 		}
 	}
 
@@ -125,5 +135,10 @@ func ParseNgxConfig(filename string) (c *NgxConfig, err error) {
 		return nil, errors.Wrap(err, "error scanner in ParseNgxConfig")
 	}
 
+	// Attach the rest of the comments to the last server
+	if len(c.Servers) > 0 {
+		c.Servers[len(c.Servers)-1].Comments += c.commentQueue.DequeueAllComments()
+	}
+
 	return c, nil
 }

+ 24 - 10
server/tool/nginx/tokenize.go

@@ -9,7 +9,6 @@ import (
 
 func (c *NgxConfig) parseServer(scanner *bufio.Scanner) {
 	server := NewNgxServer()
-	server.Directives = make(NgxDirectives)
 	for scanner.Scan() {
 		d := parseDirective(scanner)
 		switch d.Directive {
@@ -21,18 +20,21 @@ func (c *NgxConfig) parseServer(scanner *bufio.Scanner) {
 			server.parseDirective(d)
 		}
 	}
+	// Attach the rest of the comments to the last location
+	if len(server.Locations) > 0 {
+		server.Locations[len(server.Locations)-1].Comments += server.commentQueue.DequeueAllComments()
+	}
 
-	// attach comments which are over the current server
+	// Attach comments which are over the current server
 	server.Comments = c.commentQueue.DequeueAllComments()
 
-	c.Servers = append(c.Servers, *server)
+	c.Servers = append(c.Servers, server)
 }
 
 func (c *NgxConfig) parseUpstream(scanner *bufio.Scanner) {
-	upstream := NgxUpstream{}
-	upstream.Directives = make(NgxDirectives)
-	d := NgxDirective{}
+	upstream := &NgxUpstream{}
 	for scanner.Scan() {
+		d := NgxDirective{}
 		text := strings.TrimSpace(scanner.Text())
 		// escape empty line or comment line
 		if len(text) < 1 || text[0] == '#' {
@@ -51,7 +53,7 @@ func (c *NgxConfig) parseUpstream(scanner *bufio.Scanner) {
 		d.Params = strings.Trim(text[sep:], ";")
 
 		if d.Directive == Server {
-			upstream.Directives[d.Directive] = append(upstream.Directives[d.Directive], d)
+			upstream.Directives = append(upstream.Directives, &d)
 		} else if upstream.Name == "" {
 			upstream.Name = d.Directive
 		}
@@ -67,7 +69,14 @@ func (s *NgxServer) parseDirective(d NgxDirective) {
 	// handle inline comments
 	str, comments, _ := strings.Cut(orig, "#")
 
-	regExp := regexp.MustCompile("(\\S+?)\\s+{?(.+?)[;|}]")
+	if d.Directive == If {
+		d.Params = "if " + d.Params
+		d.Params = fmtCode(d.Params)
+		s.Directives = append(s.Directives, &d)
+		return
+	}
+
+	regExp := regexp.MustCompile("(\\S+?)\\s+?{?(.+?)[;|}]")
 	matchSlice := regExp.FindAllStringSubmatch(str, -1)
 
 	for k, v := range matchSlice {
@@ -90,7 +99,7 @@ func (s *NgxServer) parseDirective(d NgxDirective) {
 					// trim right ';'
 					d.TrimParams()
 					// map[directive]=>[]Params
-					s.Directives[d.Directive] = append(s.Directives[d.Directive], d)
+					s.Directives = append(s.Directives, &d)
 				}
 
 			}
@@ -100,9 +109,14 @@ func (s *NgxServer) parseDirective(d NgxDirective) {
 
 func (s *NgxServer) parseLocation(str string) {
 	path, content, _ := strings.Cut(str, "{")
+	path = strings.TrimSpace(path)
+
 	content = strings.TrimSpace(content)
 	content = strings.Trim(content, "}")
-	location := NgxLocation{
+
+	content = fmtCode(content)
+
+	location := &NgxLocation{
 		Path:    path,
 		Content: content,
 	}

+ 33 - 33
server/tool/nginx/type.go

@@ -1,74 +1,74 @@
 package nginx
 
 import (
-    "github.com/emirpasic/gods/queues/linkedlistqueue"
-    "strings"
+	"github.com/emirpasic/gods/queues/linkedlistqueue"
+	"strings"
 )
 
 type CommentQueue struct {
-    *linkedlistqueue.Queue
+	*linkedlistqueue.Queue
 }
 
 type NgxConfig struct {
-    FileName     string        `json:"file_name"`
-    Upstreams    []NgxUpstream `json:"upstreams"`
-    Servers      []NgxServer   `json:"ngx_server"`
-    commentQueue *CommentQueue
+	FileName     string         `json:"file_name"`
+	Upstreams    []*NgxUpstream `json:"upstreams"`
+	Servers      []*NgxServer   `json:"servers"`
+	Custom       string         `json:"custom"`
+	commentQueue *CommentQueue
 }
 
 type NgxServer struct {
-    ServerName   string        `json:"server_name"`
-    Directives   NgxDirectives `json:"directives"`
-    Locations    []NgxLocation `json:"locations"`
-    Comments     string        `json:"comments"`
-    commentQueue *CommentQueue
+	Directives   []*NgxDirective `json:"directives"`
+	Locations    []*NgxLocation  `json:"locations"`
+	Comments     string          `json:"comments"`
+	commentQueue *CommentQueue
 }
 
 type NgxUpstream struct {
-    Name       string        `json:"name"`
-    Directives NgxDirectives `json:"directives"`
-    Comments   string        `json:"comments"`
+	Name       string          `json:"name"`
+	Directives []*NgxDirective `json:"directives"`
+	Comments   string          `json:"comments"`
 }
 
 type NgxDirective struct {
-    Directive string `json:"directive"`
-    Params    string `json:"params"`
-    Comments  string `json:"comments"`
+	Directive string `json:"directive"`
+	Params    string `json:"params"`
+	Comments  string `json:"comments"`
 }
 
 type NgxDirectives map[string][]NgxDirective
 
 type NgxLocation struct {
-    Path     string `json:"path"`
-    Content  string `json:"content"`
-    Comments string `json:"comments"`
+	Path     string `json:"path"`
+	Content  string `json:"content"`
+	Comments string `json:"comments"`
 }
 
 func (c *CommentQueue) DequeueAllComments() (comments string) {
-    for !c.Empty() {
-        comment, ok := c.Dequeue()
+	for !c.Empty() {
+		comment, ok := c.Dequeue()
 
-        if ok {
-            comments += strings.TrimSpace(comment.(string)) + "\n"
-        }
-    }
+		if ok {
+			comments += strings.TrimSpace(comment.(string)) + "\n"
+		}
+	}
 
-    return
+	return
 }
 
 func (d *NgxDirective) Orig() string {
-    return d.Directive + " " + d.Params
+	return d.Directive + " " + d.Params
 }
 
 func (d *NgxDirective) TrimParams() {
-    d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";")
-    return
+	d.Params = strings.TrimRight(strings.TrimSpace(d.Params), ";")
+	return
 }
 
 func NewNgxServer() *NgxServer {
-    return &NgxServer{commentQueue: &CommentQueue{linkedlistqueue.New()}}
+	return &NgxServer{commentQueue: &CommentQueue{linkedlistqueue.New()}}
 }
 
 func NewNgxConfig(filename string) *NgxConfig {
-    return &NgxConfig{FileName: filename, commentQueue: &CommentQueue{linkedlistqueue.New()}}
+	return &NgxConfig{FileName: filename, commentQueue: &CommentQueue{linkedlistqueue.New()}}
 }