Преглед изворни кода

fix: server_name split issue in SAN cert

0xJacky пре 2 година
родитељ
комит
175d19a206

+ 1 - 1
frontend/src/components/StdDataDisplay/StdTable.vue

@@ -525,7 +525,7 @@ function initSortable() {
                             :okText="$gettext('OK')"
                             :title="$gettext('Are you sure you want to delete?')"
                             @confirm="destroy(record[rowKey])">
-                            <a-button type="link" size="small" v-translate>Delete</a-button>
+                            <a-button type="link" size="small">{{ $gettext('Delete') }}</a-button>
                         </a-popconfirm>
                     </template>
                 </template>

+ 1 - 5
frontend/src/views/dashboard/DashBoard.vue

@@ -164,7 +164,7 @@ function wsOnMessage(m: { data: any }) {
                     </p>
                     <p v-if="cpu_info">
                         {{ $gettext('CPU:') + ' ' }}
-                        <span class="cpu-model">{{ cpu_info[0]?.modelName }}</span>
+                        <span class="cpu-model">{{ cpu_info[0]?.modelName || 'core' }}</span>
                         <span class="cpu-mhz">{{ (cpu_info[0]?.mhz / 1000).toFixed(2) + 'GHz' }}</span>
                         * {{ cpu_info.length }}
                     </p>
@@ -303,10 +303,6 @@ function wsOnMessage(m: { data: any }) {
     }
 }
 
-.os-platform {
-    text-transform: capitalize;
-}
-
 .load-avg-describe {
     @media (max-width: 1600px) and (min-width: 1200px) {
         display: none;

+ 1 - 1
frontend/src/views/domain/DomainEdit.vue

@@ -25,7 +25,7 @@ watch(route, () => {
 const update = ref(0)
 
 const ngx_config = reactive({
-    filename: '',
+    name: '',
     upstreams: [],
     servers: []
 })

+ 2 - 1
frontend/src/views/domain/cert/Cert.vue

@@ -7,7 +7,7 @@ import ChangeCert from '@/views/domain/cert/ChangeCert.vue'
 
 const {$gettext} = useGettext()
 
-const props = defineProps(['directivesMap', 'current_server_directives', 'enabled', 'cert_info'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled', 'cert_info'])
 
 const emit = defineEmits(['callback', 'update:enabled'])
 
@@ -38,6 +38,7 @@ const enabled = computed({
         <change-cert :directives-map="props.directivesMap"/>
 
         <issue-cert
+            :config_name="config_name"
             :current_server_directives="props.current_server_directives"
             :directives-map="props.directivesMap"
             v-model:enabled="enabled"

+ 7 - 7
frontend/src/views/domain/cert/IssueCert.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
 import {useGettext} from 'vue3-gettext'
-import {computed, h, nextTick, onMounted, ref, VNode, watch} from 'vue'
+import {computed, nextTick, ref, watch} from 'vue'
 import {message} from 'ant-design-vue'
 import domain from '@/api/domain'
 import websocket from '@/lib/websocket'
@@ -8,7 +8,7 @@ import Template from '@/views/template/Template.vue'
 
 const {$gettext, interpolate} = useGettext()
 
-const props = defineProps(['directivesMap', 'current_server_directives', 'enabled'])
+const props = defineProps(['config_name', 'directivesMap', 'current_server_directives', 'enabled'])
 
 const emit = defineEmits(['changeEnabled', 'callback', 'update:enabled'])
 
@@ -50,7 +50,7 @@ function job() {
             })
         }
     }).then(() => {
-        issue_cert(name.value, callback)
+        issue_cert(props.config_name, name.value, callback)
     })
 }
 
@@ -61,13 +61,13 @@ function callback(ssl_certificate: string, ssl_certificate_key: string) {
 
 function change_auto_cert(r: boolean) {
     if (r) {
-        domain.add_auto_cert(name.value).then(() => {
+        domain.add_auto_cert(props.config_name).then(() => {
             message.success(interpolate($gettext('Auto-renewal enabled for %{name}'), {name: name.value}))
         }).catch(e => {
             message.error(e.message ?? interpolate($gettext('Enable auto-renewal failed for %{name}'), {name: name.value}))
         })
     } else {
-        domain.remove_auto_cert(name.value).then(() => {
+        domain.remove_auto_cert(props.config_name).then(() => {
             message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), {name: name.value}))
         }).catch(e => {
             message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), {name: name.value}))
@@ -86,7 +86,7 @@ function log(msg: string) {
     (logContainer.value as any as Element).scroll({top: 320, left: 0, behavior: 'smooth'})
 }
 
-const issue_cert = async (server_name: string, callback: Function) => {
+const issue_cert = async (config_name: string, server_name: string, callback: Function) => {
     progressStatus.value = 'active'
     modalClosable.value = false
     modalVisible.value = true
@@ -95,7 +95,7 @@ const issue_cert = async (server_name: string, callback: Function) => {
 
     log($gettext('Getting the certificate, please wait...'))
 
-    const ws = websocket('/api/cert/issue', false)
+    const ws = websocket(`/api/domain/${config_name}/cert`, false)
 
     ws.onopen = () => {
         ws.send(JSON.stringify({

+ 1 - 0
frontend/src/views/domain/ngx_conf/NgxConfigEditor.vue

@@ -168,6 +168,7 @@ watch(current_server_index, () => {
                     <template v-if="current_support_ssl&&enabled">
                         <cert
                             v-if="current_support_ssl"
+                            :config_name="ngx_config.name"
                             :cert_info="props.cert_info?.[k]"
                             :current_server_directives="current_server_directives"
                             :directives-map="directivesMap"

+ 1 - 1
frontend/vite.config.ts

@@ -66,7 +66,7 @@ export default defineConfig({
     server: {
         proxy: {
             '/api': {
-                target: 'https://nginx.jackyu.cn/',
+                target: 'http://127.0.0.1:9001/',
                 changeOrigin: true,
                 secure: false,
                 ws: true

+ 1 - 0
go.mod

@@ -39,6 +39,7 @@ require (
 	github.com/jpillora/s3 v1.1.4 // indirect
 	github.com/json-iterator/go v1.1.9 // indirect
 	github.com/leodido/go-urn v1.2.0 // indirect
+	github.com/lib/pq v1.10.7 // indirect
 	github.com/mattn/go-isatty v0.0.12 // indirect
 	github.com/mattn/go-sqlite3 v1.14.5 // indirect
 	github.com/miekg/dns v1.1.40 // indirect

+ 2 - 0
go.sum

@@ -280,6 +280,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
 github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
+github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE=
 github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
 github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=

+ 1 - 1
main.go

@@ -54,7 +54,7 @@ func prog(state overseer.State) {
 	}
 
 	s := gocron.NewScheduler(time.UTC)
-	job, err := s.Every(1).Hour().SingletonMode().Do(cert.AutoCert)
+	job, err := s.Every(1).Minute().SingletonMode().Do(cert.AutoObtain)
 
 	if err != nil {
 		log.Fatalf("AutoCert Job: %v, Err: %v\n", job, err)

+ 17 - 20
server/api/cert.go

@@ -87,8 +87,6 @@ func IssueCert(c *gin.Context) {
 
 	go cert.IssueCert(buffer.ServerName, logChan, errChan)
 
-	domain := strings.Join(buffer.ServerName, "_")
-
 	go handleIssueCertLogChan(ws, logChan)
 
 	// block, unless errChan closed
@@ -110,22 +108,29 @@ func IssueCert(c *gin.Context) {
 
 	close(logChan)
 
-	sslCertificatePath := nginx.GetConfPath("ssl", domain, "fullchain.cer")
-	sslCertificateKeyPath := nginx.GetConfPath("ssl", domain, "private.key")
+	certDirName := strings.Join(buffer.ServerName, "_")
+	sslCertificatePath := nginx.GetConfPath("ssl", certDirName, "fullchain.cer")
+	sslCertificateKeyPath := nginx.GetConfPath("ssl", certDirName, "private.key")
 
-	certModel, err := model.FirstOrCreateCert(domain)
+	certModel, err := model.FirstOrCreateCert(c.Param("name"))
 
 	if err != nil {
 		log.Println(err)
 	}
 
 	err = certModel.Updates(&model.Cert{
+		Domains:               buffer.ServerName,
 		SSLCertificatePath:    sslCertificatePath,
 		SSLCertificateKeyPath: sslCertificateKeyPath,
 	})
 
 	if err != nil {
 		log.Println(err)
+		err = ws.WriteJSON(IssueCertResponse{
+			Status:  Error,
+			Message: err.Error(),
+		})
+		return
 	}
 
 	err = ws.WriteJSON(IssueCertResponse{
@@ -150,9 +155,9 @@ func GetCertList(c *gin.Context) {
 	})
 }
 
-func getCert(c *gin.Context, certModel model.Cert) {
+func getCert(c *gin.Context, certModel *model.Cert) {
 	type resp struct {
-		model.Cert
+		*model.Cert
 		SSLCertification    string           `json:"ssl_certification"`
 		SSLCertificationKey string           `json:"ssl_certification_key"`
 		CertificateInfo     *CertificateInfo `json:"certificate_info,omitempty"`
@@ -202,13 +207,12 @@ func GetCert(c *gin.Context) {
 		return
 	}
 
-	getCert(c, certModel)
+	getCert(c, &certModel)
 }
 
 func AddCert(c *gin.Context) {
 	var json struct {
 		Name                  string `json:"name"`
-		Domain                string `json:"domain" binding:"required"`
 		SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required"`
 		SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
 		SSLCertification      string `json:"ssl_certification"`
@@ -217,19 +221,13 @@ func AddCert(c *gin.Context) {
 	if !BindAndValid(c, &json) {
 		return
 	}
-	certModel, err := model.FirstOrCreateCert(json.Domain)
-
-	if err != nil {
-		ErrHandler(c, err)
-		return
-	}
-
-	err = certModel.Updates(&model.Cert{
+	certModel := &model.Cert{
 		Name:                  json.Name,
-		Domain:                json.Domain,
 		SSLCertificatePath:    json.SSLCertificatePath,
 		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
-	})
+	}
+
+	err := certModel.Insert()
 
 	if err != nil {
 		ErrHandler(c, err)
@@ -291,7 +289,6 @@ func ModifyCert(c *gin.Context) {
 
 	err = certModel.Updates(&model.Cert{
 		Name:                  json.Name,
-		Domain:                json.Domain,
 		SSLCertificatePath:    json.SSLCertificatePath,
 		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
 	})

+ 364 - 373
server/api/domain.go

@@ -1,435 +1,426 @@
 package api
 
 import (
-    "github.com/0xJacky/Nginx-UI/server/model"
-    "github.com/0xJacky/Nginx-UI/server/pkg/cert"
-    "github.com/0xJacky/Nginx-UI/server/pkg/config_list"
-    "github.com/0xJacky/Nginx-UI/server/pkg/helper"
-    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-    "github.com/gin-gonic/gin"
-    "log"
-    "net/http"
-    "os"
-    "strings"
-    "time"
+	"github.com/0xJacky/Nginx-UI/server/model"
+	"github.com/0xJacky/Nginx-UI/server/pkg/cert"
+	"github.com/0xJacky/Nginx-UI/server/pkg/config_list"
+	"github.com/0xJacky/Nginx-UI/server/pkg/helper"
+	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/gin-gonic/gin"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+	"time"
 )
 
 func GetDomains(c *gin.Context) {
-    name := c.Query("name")
-    orderBy := c.Query("order_by")
-    sort := c.DefaultQuery("sort", "desc")
-
-    mySort := map[string]string{
-        "enabled": "bool",
-        "name":    "string",
-        "modify":  "time",
-    }
-
-    configFiles, err := os.ReadDir(nginx.GetConfPath("sites-available"))
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    enabledConfig, err := os.ReadDir(nginx.GetConfPath("sites-enabled"))
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    enabledConfigMap := make(map[string]bool)
-    for i := range enabledConfig {
-        enabledConfigMap[enabledConfig[i].Name()] = true
-    }
-
-    var configs []gin.H
-
-    for i := range configFiles {
-        file := configFiles[i]
-        fileInfo, _ := file.Info()
-        if !file.IsDir() {
-            if name != "" && !strings.Contains(file.Name(), name) {
-                continue
-            }
-            configs = append(configs, gin.H{
-                "name":    file.Name(),
-                "size":    fileInfo.Size(),
-                "modify":  fileInfo.ModTime(),
-                "enabled": enabledConfigMap[file.Name()],
-            })
-        }
-    }
-
-    configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
-
-    c.JSON(http.StatusOK, gin.H{
-        "data": configs,
-    })
+	name := c.Query("name")
+	orderBy := c.Query("order_by")
+	sort := c.DefaultQuery("sort", "desc")
+
+	mySort := map[string]string{
+		"enabled": "bool",
+		"name":    "string",
+		"modify":  "time",
+	}
+
+	configFiles, err := os.ReadDir(nginx.GetConfPath("sites-available"))
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	enabledConfig, err := os.ReadDir(nginx.GetConfPath("sites-enabled"))
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	enabledConfigMap := make(map[string]bool)
+	for i := range enabledConfig {
+		enabledConfigMap[enabledConfig[i].Name()] = true
+	}
+
+	var configs []gin.H
+
+	for i := range configFiles {
+		file := configFiles[i]
+		fileInfo, _ := file.Info()
+		if !file.IsDir() {
+			if name != "" && !strings.Contains(file.Name(), name) {
+				continue
+			}
+			configs = append(configs, gin.H{
+				"name":    file.Name(),
+				"size":    fileInfo.Size(),
+				"modify":  fileInfo.ModTime(),
+				"enabled": enabledConfigMap[file.Name()],
+			})
+		}
+	}
+
+	configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
+
+	c.JSON(http.StatusOK, gin.H{
+		"data": configs,
+	})
 }
 
 type CertificateInfo struct {
-    SubjectName string    `json:"subject_name"`
-    IssuerName  string    `json:"issuer_name"`
-    NotAfter    time.Time `json:"not_after"`
-    NotBefore   time.Time `json:"not_before"`
+	SubjectName string    `json:"subject_name"`
+	IssuerName  string    `json:"issuer_name"`
+	NotAfter    time.Time `json:"not_after"`
+	NotBefore   time.Time `json:"not_before"`
 }
 
 func GetDomain(c *gin.Context) {
-    rewriteName, ok := c.Get("rewriteConfigFileName")
+	rewriteName, ok := c.Get("rewriteConfigFileName")
 
-    name := c.Param("name")
+	name := c.Param("name")
 
-    // for modify filename
-    if ok {
-        name = rewriteName.(string)
-    }
+	// for modify filename
+	if ok {
+		name = rewriteName.(string)
+	}
 
-    path := nginx.GetConfPath("sites-available", name)
+	path := nginx.GetConfPath("sites-available", name)
 
-    enabled := true
-    if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
-        enabled = false
-    }
+	enabled := true
+	if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
+		enabled = false
+	}
 
-    c.Set("maybe_error", "nginx_config_syntax_error")
-    config, err := nginx.ParseNgxConfig(path)
+	c.Set("maybe_error", "nginx_config_syntax_error")
+	config, err := nginx.ParseNgxConfig(path)
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    c.Set("maybe_error", "")
+	c.Set("maybe_error", "")
 
-    certInfoMap := make(map[int]CertificateInfo)
-    var serverName string
-    for serverIdx, server := range config.Servers {
-        for _, directive := range server.Directives {
+	certInfoMap := make(map[int]CertificateInfo)
+	for serverIdx, server := range config.Servers {
+		for _, directive := range server.Directives {
+			if directive.Directive == "ssl_certificate" {
 
-            if directive.Directive == "server_name" {
-                serverName = strings.ReplaceAll(directive.Params, " ", "_")
-                continue
-            }
+				pubKey, err := cert.GetCertInfo(directive.Params)
 
-            if directive.Directive == "ssl_certificate" {
+				if err != nil {
+					log.Println("Failed to get certificate information", err)
+					break
+				}
 
-                pubKey, err := cert.GetCertInfo(directive.Params)
+				certInfoMap[serverIdx] = CertificateInfo{
+					SubjectName: pubKey.Subject.CommonName,
+					IssuerName:  pubKey.Issuer.CommonName,
+					NotAfter:    pubKey.NotAfter,
+					NotBefore:   pubKey.NotBefore,
+				}
 
-                if err != nil {
-                    log.Println("Failed to get certificate information", err)
-                    break
-                }
+				break
+			}
+		}
+	}
 
-                certInfoMap[serverIdx] = CertificateInfo{
-                    SubjectName: pubKey.Subject.CommonName,
-                    IssuerName:  pubKey.Issuer.CommonName,
-                    NotAfter:    pubKey.NotAfter,
-                    NotBefore:   pubKey.NotBefore,
-                }
+	certModel, _ := model.FirstCert(name)
 
-                break
-            }
-        }
-    }
+	c.Set("maybe_error", "nginx_config_syntax_error")
 
-    certModel, _ := model.FirstCert(serverName)
-
-    c.Set("maybe_error", "nginx_config_syntax_error")
-
-    c.JSON(http.StatusOK, gin.H{
-        "enabled":   enabled,
-        "name":      name,
-        "config":    config.FmtCode(),
-        "tokenized": config,
-        "auto_cert": certModel.AutoCert == model.AutoCertEnabled,
-        "cert_info": certInfoMap,
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"enabled":   enabled,
+		"name":      name,
+		"config":    config.FmtCode(),
+		"tokenized": config,
+		"auto_cert": certModel.AutoCert == model.AutoCertEnabled,
+		"cert_info": certInfoMap,
+	})
 
 }
 
 func SaveDomain(c *gin.Context) {
-    name := c.Param("name")
-
-    if name == "" {
-        c.JSON(http.StatusNotAcceptable, gin.H{
-            "message": "param name is empty",
-        })
-        return
-    }
-
-    var json struct {
-        Name      string `json:"name" binding:"required"`
-        Content   string `json:"content" binding:"required"`
-        Overwrite bool   `json:"overwrite"`
-    }
-
-    if !BindAndValid(c, &json) {
-        return
-    }
-
-    path := nginx.GetConfPath("sites-available", name)
-
-    if !json.Overwrite && helper.FileExists(path) {
-        c.JSON(http.StatusNotAcceptable, gin.H{
-            "message": "File exists",
-        })
-        return
-    }
-
-    err := os.WriteFile(path, []byte(json.Content), 0644)
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
-    // rename the config file if needed
-    if name != json.Name {
-        newPath := nginx.GetConfPath("sites-available", json.Name)
-        // check if dst file exists, do not rename
-        if helper.FileExists(newPath) {
-            c.JSON(http.StatusNotAcceptable, gin.H{
-                "message": "File exists",
-            })
-            return
-        }
-        // recreate soft link
-        if helper.FileExists(enabledConfigFilePath) {
-            _ = os.Remove(enabledConfigFilePath)
-            enabledConfigFilePath = nginx.GetConfPath("sites-enabled", json.Name)
-            err = os.Symlink(newPath, enabledConfigFilePath)
-
-            if err != nil {
-                ErrHandler(c, err)
-                return
-            }
-        }
-
-        err = os.Rename(path, newPath)
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-
-        name = json.Name
-        c.Set("rewriteConfigFileName", name)
-    }
-
-    enabledConfigFilePath = nginx.GetConfPath("sites-enabled", name)
-    if helper.FileExists(enabledConfigFilePath) {
-        // Test nginx configuration
-        output := nginx.TestConf()
-        if nginx.GetLogLevel(output) >= nginx.Warn {
-            c.JSON(http.StatusInternalServerError, gin.H{
-                "message": output,
-                "error":   "nginx_config_syntax_error",
-            })
-            return
-        }
-
-        output = nginx.Reload()
-
-        if nginx.GetLogLevel(output) >= nginx.Warn {
-            c.JSON(http.StatusInternalServerError, gin.H{
-                "message": output,
-            })
-            return
-        }
-    }
-
-    GetDomain(c)
+	name := c.Param("name")
+
+	if name == "" {
+		c.JSON(http.StatusNotAcceptable, gin.H{
+			"message": "param name is empty",
+		})
+		return
+	}
+
+	var json struct {
+		Name      string `json:"name" binding:"required"`
+		Content   string `json:"content" binding:"required"`
+		Overwrite bool   `json:"overwrite"`
+	}
+
+	if !BindAndValid(c, &json) {
+		return
+	}
+
+	path := nginx.GetConfPath("sites-available", name)
+
+	if !json.Overwrite && helper.FileExists(path) {
+		c.JSON(http.StatusNotAcceptable, gin.H{
+			"message": "File exists",
+		})
+		return
+	}
+
+	err := os.WriteFile(path, []byte(json.Content), 0644)
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	enabledConfigFilePath := nginx.GetConfPath("sites-enabled", name)
+	// rename the config file if needed
+	if name != json.Name {
+		newPath := nginx.GetConfPath("sites-available", json.Name)
+		// check if dst file exists, do not rename
+		if helper.FileExists(newPath) {
+			c.JSON(http.StatusNotAcceptable, gin.H{
+				"message": "File exists",
+			})
+			return
+		}
+		// recreate soft link
+		if helper.FileExists(enabledConfigFilePath) {
+			_ = os.Remove(enabledConfigFilePath)
+			enabledConfigFilePath = nginx.GetConfPath("sites-enabled", json.Name)
+			err = os.Symlink(newPath, enabledConfigFilePath)
+
+			if err != nil {
+				ErrHandler(c, err)
+				return
+			}
+		}
+
+		err = os.Rename(path, newPath)
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+
+		name = json.Name
+		c.Set("rewriteConfigFileName", name)
+	}
+
+	enabledConfigFilePath = nginx.GetConfPath("sites-enabled", name)
+	if helper.FileExists(enabledConfigFilePath) {
+		// Test nginx configuration
+		output := nginx.TestConf()
+		if nginx.GetLogLevel(output) >= nginx.Warn {
+			c.JSON(http.StatusInternalServerError, gin.H{
+				"message": output,
+				"error":   "nginx_config_syntax_error",
+			})
+			return
+		}
+
+		output = nginx.Reload()
+
+		if nginx.GetLogLevel(output) >= nginx.Warn {
+			c.JSON(http.StatusInternalServerError, gin.H{
+				"message": output,
+			})
+			return
+		}
+	}
+
+	GetDomain(c)
 }
 
 func EnableDomain(c *gin.Context) {
-    configFilePath := nginx.GetConfPath("sites-available", c.Param("name"))
-    enabledConfigFilePath := nginx.GetConfPath("sites-enabled", c.Param("name"))
-
-    _, err := os.Stat(configFilePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
-        err = os.Symlink(configFilePath, enabledConfigFilePath)
-
-        if err != nil {
-            ErrHandler(c, err)
-            return
-        }
-    }
-
-    // Test nginx config, if not pass then disable the site.
-    output := nginx.TestConf()
-
-    if nginx.GetLogLevel(output) >= nginx.Warn {
-        _ = os.Remove(enabledConfigFilePath)
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": output,
-        })
-        return
-    }
-
-    output = nginx.Reload()
-
-    if nginx.GetLogLevel(output) >= nginx.Warn {
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": output,
-        })
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
+	configFilePath := nginx.GetConfPath("sites-available", c.Param("name"))
+	enabledConfigFilePath := nginx.GetConfPath("sites-enabled", c.Param("name"))
+
+	_, err := os.Stat(configFilePath)
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
+		err = os.Symlink(configFilePath, enabledConfigFilePath)
+
+		if err != nil {
+			ErrHandler(c, err)
+			return
+		}
+	}
+
+	// Test nginx config, if not pass then disable the site.
+	output := nginx.TestConf()
+
+	if nginx.GetLogLevel(output) >= nginx.Warn {
+		_ = os.Remove(enabledConfigFilePath)
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": output,
+		})
+		return
+	}
+
+	output = nginx.Reload()
+
+	if nginx.GetLogLevel(output) >= nginx.Warn {
+		c.JSON(http.StatusInternalServerError, gin.H{
+			"message": output,
+		})
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
 }
 
 func DisableDomain(c *gin.Context) {
-    enabledConfigFilePath := nginx.GetConfPath("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
-    }
-
-    // delete auto cert record
-    certModel := model.Cert{Domain: c.Param("name")}
-    err = certModel.Remove()
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    output := nginx.Reload()
-
-    if nginx.GetLogLevel(output) >= nginx.Warn {
-        c.JSON(http.StatusInternalServerError, gin.H{
-            "message": output,
-        })
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
+	enabledConfigFilePath := nginx.GetConfPath("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
+	}
+
+	// delete auto cert record
+	certModel := model.Cert{Filename: c.Param("name")}
+	err = certModel.Remove()
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+
+	output := nginx.Reload()
+
+	if nginx.GetLogLevel(output) >= nginx.Warn {
+		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 := nginx.GetConfPath("sites-available", name)
-    enabledPath := nginx.GetConfPath("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
-    }
-
-    certModel := model.Cert{Domain: name}
-    _ = certModel.Remove()
-
-    err = os.Remove(availablePath)
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-
-    c.JSON(http.StatusOK, gin.H{
-        "message": "ok",
-    })
+	var err error
+	name := c.Param("name")
+	availablePath := nginx.GetConfPath("sites-available", name)
+	enabledPath := nginx.GetConfPath("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
+	}
+
+	certModel := model.Cert{Filename: name}
+	_ = certModel.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")
-    domain = strings.ReplaceAll(domain, " ", "_")
-    certModel, err := model.FirstOrCreateCert(domain)
+	name := c.Param("name")
+	certModel, err := model.FirstOrCreateCert(name)
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    err = certModel.Updates(&model.Cert{
-        AutoCert: model.AutoCertEnabled,
-    })
+	err = certModel.Updates(&model.Cert{
+		AutoCert: model.AutoCertEnabled,
+	})
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    c.JSON(http.StatusOK, certModel)
+	c.JSON(http.StatusOK, certModel)
 }
 
 func RemoveDomainFromAutoCert(c *gin.Context) {
-    domain := c.Param("domain")
-    domain = strings.ReplaceAll(domain, " ", "_")
-    certModel := model.Cert{
-        Domain: domain,
-    }
-
-    err := certModel.Updates(&model.Cert{
-        AutoCert: model.AutoCertDisabled,
-    })
-
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
-    c.JSON(http.StatusOK, nil)
+	name := c.Param("name")
+	certModel := model.Cert{
+		Filename: name,
+	}
+
+	err := certModel.Updates(&model.Cert{
+		AutoCert: model.AutoCertDisabled,
+	})
+
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, nil)
 }
 
 func DuplicateSite(c *gin.Context) {
-    name := c.Param("name")
+	name := c.Param("name")
 
-    var json struct {
-        Name string `json:"name" binding:"required"`
-    }
+	var json struct {
+		Name string `json:"name" binding:"required"`
+	}
 
-    if !BindAndValid(c, &json) {
-        return
-    }
+	if !BindAndValid(c, &json) {
+		return
+	}
 
-    src := nginx.GetConfPath("sites-available", name)
-    dst := nginx.GetConfPath("sites-available", json.Name)
+	src := nginx.GetConfPath("sites-available", name)
+	dst := nginx.GetConfPath("sites-available", json.Name)
 
-    if helper.FileExists(dst) {
-        c.JSON(http.StatusNotAcceptable, gin.H{
-            "message": "File exists",
-        })
-        return
-    }
+	if helper.FileExists(dst) {
+		c.JSON(http.StatusNotAcceptable, gin.H{
+			"message": "File exists",
+		})
+		return
+	}
 
-    _, err := helper.CopyFile(src, dst)
+	_, err := helper.CopyFile(src, dst)
 
-    if err != nil {
-        ErrHandler(c, err)
-        return
-    }
+	if err != nil {
+		ErrHandler(c, err)
+		return
+	}
 
-    c.JSON(http.StatusOK, gin.H{
-        "dst": dst,
-    })
+	c.JSON(http.StatusOK, gin.H{
+		"dst": dst,
+	})
 }

+ 20 - 12
server/model/cert.go

@@ -2,6 +2,7 @@ package model
 
 import (
 	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+	"github.com/lib/pq"
 	"os"
 )
 
@@ -10,28 +11,35 @@ const (
 	AutoCertDisabled = -1
 )
 
+type CertDomains []string
+
 type Cert struct {
 	Model
-	Name                  string `json:"name"`
-	Domain                string `json:"domain"`
-	SSLCertificatePath    string `json:"ssl_certificate_path"`
-	SSLCertificateKeyPath string `json:"ssl_certificate_key_path"`
-	AutoCert              int    `json:"auto_cert"`
+	Name                  string         `json:"name"`
+	Domains               pq.StringArray `json:"domains" gorm:"type:text[]"`
+	Filename              string         `json:"filename"`
+	SSLCertificatePath    string         `json:"ssl_certificate_path"`
+	SSLCertificateKeyPath string         `json:"ssl_certificate_key_path"`
+	AutoCert              int            `json:"auto_cert"`
 }
 
-func FirstCert(domain string) (c Cert, err error) {
+func FirstCert(confName string) (c Cert, err error) {
 	err = db.First(&c, &Cert{
-		Domain: domain,
+		Filename: confName,
 	}).Error
 
 	return
 }
 
-func FirstOrCreateCert(domain string) (c Cert, err error) {
-	err = db.FirstOrCreate(&c, &Cert{Domain: domain}).Error
+func FirstOrCreateCert(confName string) (c Cert, err error) {
+	err = db.FirstOrCreate(&c, &Cert{Filename: confName}).Error
 	return
 }
 
+func (c *Cert) Insert() error {
+	return db.Create(c).Error
+}
+
 func GetAutoCertList() (c []Cert) {
 	var t []Cert
 	db.Where("auto_cert", AutoCertEnabled).Find(&t)
@@ -49,7 +57,7 @@ func GetAutoCertList() (c []Cert) {
 	}
 
 	for _, v := range t {
-		if enabledConfigMap[v.Domain] == true {
+		if enabledConfigMap[v.Filename] == true {
 			c = append(c, v)
 		}
 	}
@@ -76,9 +84,9 @@ func FirstCertByID(id int) (c Cert, err error) {
 }
 
 func (c *Cert) Updates(n *Cert) error {
-	return db.Model(c).Updates(n).Error
+	return db.Model(&Cert{}).Where("filename", c.Filename).Updates(n).Error
 }
 
 func (c *Cert) Remove() error {
-	return db.Where("domain", c.Domain).Delete(c).Error
+	return db.Where("filename", c.Filename).Delete(c).Error
 }

+ 9 - 15
server/pkg/cert/auto_cert.go

@@ -3,7 +3,6 @@ package cert
 import (
 	"github.com/0xJacky/Nginx-UI/server/model"
 	"log"
-	"strings"
 	"time"
 )
 
@@ -19,7 +18,7 @@ func handleIssueCertLogChan(logChan chan string) {
 	}
 }
 
-func AutoCert() {
+func AutoObtain() {
 	defer func() {
 		if err := recover(); err != nil {
 			log.Println("[AutoCert] Recover", err)
@@ -27,19 +26,12 @@ func AutoCert() {
 	}()
 	log.Println("[AutoCert] Start")
 	autoCertList := model.GetAutoCertList()
-	for i := range autoCertList {
-		domain := autoCertList[i].Domain
-
-		certModel, err := model.FirstCert(domain)
-
-		if err != nil {
-			log.Println("[AutoCert] Error get certificate from database", err)
-			continue
-		}
+	for _, certModel := range autoCertList {
+		confName := certModel.Filename
 
 		if certModel.SSLCertificatePath == "" {
 			log.Println("[AutoCert] Error ssl_certificate_path is empty, " +
-				"try to reopen auto-cert for this domain:" + domain)
+				"try to reopen auto-cert for this config:" + confName)
 			continue
 		}
 
@@ -49,16 +41,17 @@ func AutoCert() {
 			// Get certificate info error, ignore this domain
 			continue
 		}
-		// before 1 mo
-		if time.Now().Before(cert.NotBefore.AddDate(0, 1, 0)) {
+		// every week
+		if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
 			continue
 		}
+		//
 		// after 1 mo, reissue certificate
 		logChan := make(chan string, 1)
 		errChan := make(chan error, 1)
 
 		// support SAN certification
-		go IssueCert(strings.Split(domain, "_"), logChan, errChan)
+		go IssueCert(certModel.Domains, logChan, errChan)
 
 		go handleIssueCertLogChan(logChan)
 
@@ -69,4 +62,5 @@ func AutoCert() {
 
 		close(logChan)
 	}
+	log.Println("[AutoCert] End")
 }

+ 137 - 130
server/pkg/cert/cert.go

@@ -1,148 +1,155 @@
 package cert
 
 import (
-	"crypto"
-	"crypto/ecdsa"
-	"crypto/elliptic"
-	"crypto/rand"
-	"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
-	"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"
-	"github.com/pkg/errors"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
+    "crypto"
+    "crypto/ecdsa"
+    "crypto/elliptic"
+    "crypto/rand"
+    "crypto/tls"
+    "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
+    "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"
+    "github.com/pkg/errors"
+    "log"
+    "net/http"
+    "os"
+    "path/filepath"
+    "strings"
 )
 
 // MyUser You'll need a user or account type that implements acme.User
 type MyUser struct {
-	Email        string
-	Registration *registration.Resource
-	key          crypto.PrivateKey
+    Email        string
+    Registration *registration.Resource
+    key          crypto.PrivateKey
 }
 
 func (u *MyUser) GetEmail() string {
-	return u.Email
+    return u.Email
 }
 func (u *MyUser) GetRegistration() *registration.Resource {
-	return u.Registration
+    return u.Registration
 }
 func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
-	return u.key
+    return u.key
 }
 
 func IssueCert(domain []string, logChan chan string, errChan chan error) {
-	defer func() {
-		if err := recover(); err != nil {
-			log.Println("Issue Cert recover", err)
-		}
-	}()
-
-	// Create a user. New accounts need an email and private key to start.
-	logChan <- "Generating private key for registering account"
-	privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
-	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert generate key error")
-		return
-	}
-
-	logChan <- "Preparing lego configurations"
-	myUser := MyUser{
-		Email: settings.ServerSettings.Email,
-		key:   privateKey,
-	}
-
-	config := lego.NewConfig(&myUser)
-
-	if settings.ServerSettings.Demo {
-		config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
-	}
-
-	if settings.ServerSettings.CADir != "" {
-		config.CADirURL = settings.ServerSettings.CADir
-	}
-
-	config.Certificate.KeyType = certcrypto.RSA2048
-
-	logChan <- "Creating client facilitates communication with the CA server"
-	// A client facilitates communication with the CA server.
-	client, err := lego.NewClient(config)
-	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert new client error")
-		return
-	}
-
-	logChan <- "Using HTTP01 challenge provider"
-	err = client.Challenge.SetHTTP01Provider(
-		http01.NewProviderServer("",
-			settings.ServerSettings.HTTPChallengePort,
-		),
-	)
-
-	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert challenge fail")
-		return
-	}
-
-	// New users will need to register
-	logChan <- "Registering user"
-	reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
-	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert register fail")
-		return
-	}
-	myUser.Registration = reg
-
-	request := certificate.ObtainRequest{
-		Domains: domain,
-		Bundle:  true,
-	}
-
-	logChan <- "Obtaining certificate"
-	certificates, err := client.Certificate.Obtain(request)
-	if err != nil {
-		errChan <- errors.Wrap(err, "issue cert fail to obtain")
-		return
-	}
-	name := strings.Join(domain, "_")
-	saveDir := nginx.GetConfPath("ssl/" + name)
-	if _, err = os.Stat(saveDir); os.IsNotExist(err) {
-		err = os.MkdirAll(saveDir, 0755)
-		if err != nil {
-			errChan <- errors.Wrap(err, "issue cert fail to create")
-			return
-		}
-	}
-
-	// Each certificate comes back with the cert bytes, the bytes of the client's
-	// private key, and a certificate URL. SAVE THESE TO DISK.
-	logChan <- "Writing certificate to disk"
-	err = os.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
-		certificates.Certificate, 0644)
-
-	if err != nil {
-		errChan <- errors.Wrap(err, "error issue cert write fullchain.cer")
-		return
-	}
-
-	logChan <- "Writing certificate private key to disk"
-	err = os.WriteFile(filepath.Join(saveDir, "private.key"),
-		certificates.PrivateKey, 0644)
-
-	if err != nil {
-		errChan <- errors.Wrap(err, "error issue cert write key")
-		return
-	}
-
-	close(errChan)
-	logChan <- "Reloading nginx"
-
-	nginx.Reload()
-
-	logChan <- "Finished"
+    defer func() {
+        if err := recover(); err != nil {
+            log.Println("Issue Cert recover", err)
+        }
+    }()
+
+    // Create a user. New accounts need an email and private key to start.
+    logChan <- "Generating private key for registering account"
+    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
+    if err != nil {
+        errChan <- errors.Wrap(err, "issue cert generate key error")
+        return
+    }
+
+    logChan <- "Preparing lego configurations"
+    myUser := MyUser{
+        Email: settings.ServerSettings.Email,
+        key:   privateKey,
+    }
+
+    config := lego.NewConfig(&myUser)
+
+    if settings.ServerSettings.Demo {
+        config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
+    }
+
+    if settings.ServerSettings.CADir != "" {
+        config.CADirURL = settings.ServerSettings.CADir
+        if config.HTTPClient != nil {
+            config.HTTPClient.Transport = &http.Transport{
+                TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+            }
+        }
+    }
+
+    config.Certificate.KeyType = certcrypto.RSA2048
+
+    logChan <- "Creating client facilitates communication with the CA server"
+    // A client facilitates communication with the CA server.
+    client, err := lego.NewClient(config)
+    if err != nil {
+        errChan <- errors.Wrap(err, "issue cert new client error")
+        return
+    }
+
+    logChan <- "Using HTTP01 challenge provider"
+    err = client.Challenge.SetHTTP01Provider(
+        http01.NewProviderServer("",
+            settings.ServerSettings.HTTPChallengePort,
+        ),
+    )
+
+    if err != nil {
+        errChan <- errors.Wrap(err, "issue cert challenge fail")
+        return
+    }
+
+    // New users will need to register
+    logChan <- "Registering user"
+    reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
+    if err != nil {
+        errChan <- errors.Wrap(err, "issue cert register fail")
+        return
+    }
+    myUser.Registration = reg
+
+    request := certificate.ObtainRequest{
+        Domains: domain,
+        Bundle:  true,
+    }
+
+    logChan <- "Obtaining certificate"
+    certificates, err := client.Certificate.Obtain(request)
+    if err != nil {
+        errChan <- errors.Wrap(err, "issue cert fail to obtain")
+        return
+    }
+    name := strings.Join(domain, "_")
+    saveDir := nginx.GetConfPath("ssl/" + name)
+    if _, err = os.Stat(saveDir); os.IsNotExist(err) {
+        err = os.MkdirAll(saveDir, 0755)
+        if err != nil {
+            errChan <- errors.Wrap(err, "issue cert fail to create")
+            return
+        }
+    }
+
+    // Each certificate comes back with the cert bytes, the bytes of the client's
+    // private key, and a certificate URL. SAVE THESE TO DISK.
+    logChan <- "Writing certificate to disk"
+    err = os.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
+        certificates.Certificate, 0644)
+
+    if err != nil {
+        errChan <- errors.Wrap(err, "error issue cert write fullchain.cer")
+        return
+    }
+
+    logChan <- "Writing certificate private key to disk"
+    err = os.WriteFile(filepath.Join(saveDir, "private.key"),
+        certificates.PrivateKey, 0644)
+
+    if err != nil {
+        errChan <- errors.Wrap(err, "error issue cert write key")
+        return
+    }
+
+    close(errChan)
+    logChan <- "Reloading nginx"
+
+    nginx.Reload()
+
+    logChan <- "Finished"
 }

+ 33 - 30
server/pkg/nginx/type.go

@@ -1,61 +1,64 @@
 package nginx
 
 import (
-	"github.com/tufanbarisyildirim/gonginx"
-	"strings"
+    "github.com/tufanbarisyildirim/gonginx"
+    "path"
+    "strings"
 )
 
 type NgxConfig struct {
-	FileName  string         `json:"file_name"`
-	Upstreams []*NgxUpstream `json:"upstreams"`
-	Servers   []*NgxServer   `json:"servers"`
-	Custom    string         `json:"custom"`
-	c         *gonginx.Config
+    FileName  string         `json:"file_name"`
+    Name      string         `json:"name"`
+    Upstreams []*NgxUpstream `json:"upstreams"`
+    Servers   []*NgxServer   `json:"servers"`
+    Custom    string         `json:"custom"`
+    c         *gonginx.Config
 }
 
 type NgxServer struct {
-	Directives []*NgxDirective `json:"directives"`
-	Locations  []*NgxLocation  `json:"locations"`
-	Comments   string          `json:"comments"`
+    Directives []*NgxDirective `json:"directives"`
+    Locations  []*NgxLocation  `json:"locations"`
+    Comments   string          `json:"comments"`
 }
 
 type NgxUpstream struct {
-	Name       string          `json:"name"`
-	Directives []*NgxDirective `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 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 (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{
-		Locations:  make([]*NgxLocation, 0),
-		Directives: make([]*NgxDirective, 0),
-	}
+    return &NgxServer{
+        Locations:  make([]*NgxLocation, 0),
+        Directives: make([]*NgxDirective, 0),
+    }
 }
 
 func NewNgxConfig(filename string) *NgxConfig {
-	return &NgxConfig{
-		FileName:  filename,
-		Upstreams: make([]*NgxUpstream, 0),
-	}
+    return &NgxConfig{
+        FileName:  filename,
+        Upstreams: make([]*NgxUpstream, 0),
+        Name:      path.Base(filename),
+    }
 }

+ 3 - 4
server/router/routers.go

@@ -76,6 +76,7 @@ func InitRouter() *gin.Engine {
 			g.DELETE("domain/:name", api.DeleteDomain)
 			// duplicate site
 			g.POST("domain/:name/duplicate", api.DuplicateSite)
+			g.GET("domain/:name/cert", api.IssueCert)
 
 			g.GET("configs", api.GetConfigs)
 			g.GET("config/*name", api.GetConfig)
@@ -90,17 +91,15 @@ func InitRouter() *gin.Engine {
 			g.GET("template/blocks", api.GetTemplateBlockList)
 			g.GET("template/block/:name", api.GetTemplateBlock)
 
-			g.GET("cert/issue", api.IssueCert)
-
 			g.GET("certs", api.GetCertList)
 			g.GET("cert/:id", api.GetCert)
 			g.POST("cert", api.AddCert)
 			g.POST("cert/:id", api.ModifyCert)
 			g.DELETE("cert/:id", api.RemoveCert)
 			// Add domain to auto-renew cert list
-			g.POST("auto_cert/:domain", api.AddDomainToAutoCert)
+			g.POST("auto_cert/:name", api.AddDomainToAutoCert)
 			// Delete domain from auto-renew cert list
-			g.DELETE("auto_cert/:domain", api.RemoveDomainFromAutoCert)
+			g.DELETE("auto_cert/:name", api.RemoveDomainFromAutoCert)
 
 			// pty
 			g.GET("pty", api.Pty)