wip: ssl manage panel #52, #29

This commit is contained in:
0xJacky 2023-01-03 00:24:51 +08:00
parent 4a3e32a921
commit a5b9bb88d6
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
18 changed files with 622 additions and 295 deletions

5
frontend/src/api/cert.ts Normal file
View file

@ -0,0 +1,5 @@
import Curd from '@/api/curd'
const cert = new Curd('/cert')
export default cert

View file

@ -13,13 +13,13 @@ class Domain extends Curd {
get_template() {
return http.get('template')
}
add_auto_cert(domain: string) {
return http.post('cert/' + domain)
return http.post('auto_cert/' + domain)
}
remove_auto_cert(domain: string) {
return http.delete('cert/' + domain)
return http.delete('auto_cert/' + domain)
}
}

View file

@ -17,7 +17,8 @@ msgstr "About"
msgid "Access Logs"
msgstr ""
#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
#: src/views/user/User.vue:43
msgid "Action"
msgstr "Action"
@ -205,6 +206,10 @@ msgstr ""
msgid "Development Mode"
msgstr "Development Mode"
#: src/views/config/config.ts:20
msgid "Dir"
msgstr ""
#: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
msgid "Directive"
msgstr "Directive"
@ -308,6 +313,10 @@ msgstr "Failed to enable %{msg}"
msgid "Failed to get certificate information"
msgstr ""
#: src/views/config/config.ts:22
msgid "File"
msgstr ""
#: src/views/other/Error.vue:3 src/views/other/Error.vue:4
msgid "File Not Found"
msgstr "File Not Found"
@ -444,7 +453,8 @@ msgstr "Modify Config"
msgid "Modify Config"
msgstr "Modify Config"
#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
#: src/views/domain/DomainList.vue:15
msgid "Name"
msgstr "Name"
@ -730,7 +740,12 @@ msgstr ""
msgid "Theme"
msgstr ""
#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
#: src/language/constants.ts:23 src/views/config/config.ts:14
msgid "Type"
msgstr ""
#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
#: src/views/user/User.vue:37
msgid "Updated at"
msgstr "Updated at"

View file

@ -11,6 +11,7 @@ msgstr ""
msgid "Access Logs"
msgstr ""
#: src/views/config/config.ts:36
#: src/views/domain/DomainList.vue:47
#: src/views/user/User.vue:43
msgid "Action"
@ -203,6 +204,10 @@ msgstr ""
msgid "Development Mode"
msgstr ""
#: src/views/config/config.ts:20
msgid "Dir"
msgstr ""
#: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
msgid "Directive"
msgstr ""
@ -320,6 +325,10 @@ msgstr ""
msgid "Failed to get certificate information"
msgstr ""
#: src/views/config/config.ts:22
msgid "File"
msgstr ""
#: src/views/other/Error.vue:3
#: src/views/other/Error.vue:4
msgid "File Not Found"
@ -456,6 +465,7 @@ msgstr ""
msgid "Modify Config"
msgstr ""
#: src/views/config/config.ts:9
#: src/views/domain/DomainEdit.vue:36
#: src/views/domain/DomainList.vue:15
msgid "Name"
@ -747,6 +757,12 @@ msgstr ""
msgid "Theme"
msgstr ""
#: src/language/constants.ts:23
#: src/views/config/config.ts:14
msgid "Type"
msgstr ""
#: src/views/config/config.ts:29
#: src/views/domain/DomainList.vue:41
#: src/views/user/User.vue:37
msgid "Updated at"

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -20,7 +20,8 @@ msgstr "关于"
msgid "Access Logs"
msgstr "访问日志"
#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
#: src/views/user/User.vue:43
msgid "Action"
msgstr "操作"
@ -204,6 +205,10 @@ msgstr "删除站点: %{site_name}"
msgid "Development Mode"
msgstr "开发模式"
#: src/views/config/config.ts:20
msgid "Dir"
msgstr "目录"
#: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
msgid "Directive"
msgstr "指令"
@ -307,6 +312,10 @@ msgstr "启用失败 %{msg}"
msgid "Failed to get certificate information"
msgstr "获取证书信息失败"
#: src/views/config/config.ts:22
msgid "File"
msgstr "文件"
#: src/views/other/Error.vue:3 src/views/other/Error.vue:4
msgid "File Not Found"
msgstr "未找到文件"
@ -440,7 +449,8 @@ msgstr "修改"
msgid "Modify Config"
msgstr "修改配置文件"
#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
#: src/views/domain/DomainList.vue:15
msgid "Name"
msgstr "名称"
@ -717,7 +727,12 @@ msgstr "用户名或密码错误"
msgid "Theme"
msgstr "主题"
#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
#: src/language/constants.ts:23 src/views/config/config.ts:14
msgid "Type"
msgstr "类型"
#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
#: src/views/user/User.vue:37
msgid "Updated at"
msgstr "修改时间"

View file

@ -21,7 +21,8 @@ msgstr "關於"
msgid "Access Logs"
msgstr "訪問日誌"
#: src/views/domain/DomainList.vue:47 src/views/user/User.vue:43
#: src/views/config/config.ts:36 src/views/domain/DomainList.vue:47
#: src/views/user/User.vue:43
msgid "Action"
msgstr "操作"
@ -205,6 +206,10 @@ msgstr "刪除站點:%{site_name}"
msgid "Development Mode"
msgstr "開發模式"
#: src/views/config/config.ts:20
msgid "Dir"
msgstr ""
#: src/views/domain/ngx_conf/directive/DirectiveAdd.vue:20
msgid "Directive"
msgstr "指令"
@ -308,6 +313,10 @@ msgstr "啟用失敗 %{msg}"
msgid "Failed to get certificate information"
msgstr "獲取證書信息失敗"
#: src/views/config/config.ts:22
msgid "File"
msgstr ""
#: src/views/other/Error.vue:3 src/views/other/Error.vue:4
msgid "File Not Found"
msgstr "未找到檔案"
@ -443,7 +452,8 @@ msgstr "修改"
msgid "Modify Config"
msgstr "修改配置"
#: src/views/domain/DomainEdit.vue:36 src/views/domain/DomainList.vue:15
#: src/views/config/config.ts:9 src/views/domain/DomainEdit.vue:36
#: src/views/domain/DomainList.vue:15
msgid "Name"
msgstr "名稱"
@ -724,7 +734,12 @@ msgstr "用戶名或密碼不正確"
msgid "Theme"
msgstr "外觀樣式"
#: src/views/domain/DomainList.vue:41 src/views/user/User.vue:37
#: src/language/constants.ts:23 src/views/config/config.ts:14
msgid "Type"
msgstr ""
#: src/views/config/config.ts:29 src/views/domain/DomainList.vue:41
#: src/views/user/User.vue:37
msgid "Updated at"
msgstr "修改時間"

View file

@ -10,7 +10,8 @@ import {
InfoCircleOutlined,
UserOutlined,
FileTextOutlined,
SettingOutlined
SettingOutlined,
SafetyCertificateOutlined
} from '@ant-design/icons-vue'
const {$gettext} = gettext
@ -91,6 +92,14 @@ export const routes = [
hiddenInSidebar: true
}
},
{
path: 'cert',
name: () => $gettext('Certification'),
component: () => import('@/views/cert/Cert.vue'),
meta: {
icon: SafetyCertificateOutlined
}
},
{
path: 'terminal',
name: () => $gettext('Terminal'),

View file

@ -1 +1 @@
{"version":"1.7.0","build_id":62,"total_build":132}
{"version":"1.7.0","build_id":63,"total_build":133}

View file

@ -0,0 +1,88 @@
<script setup lang="tsx">
import {useGettext} from 'vue3-gettext'
import {input} from '@/components/StdDataEntry'
import {customRender, datetime} from '@/components/StdDataDisplay/StdTableTransformer'
import {h} from 'vue'
import {Badge} from 'ant-design-vue'
import cert from '@/api/cert'
import StdCurd from '@/components/StdDataDisplay/StdCurd.vue'
const {$gettext} = useGettext()
const columns = [{
title: () => $gettext('Name'),
dataIndex: 'name',
sorter: true,
pithy: true,
customRender: (args: customRender) => {
const {text, record} = args
if (!text) {
return h('div', record.domain)
}
return h('div', text)
},
edit: {
type: input
},
search: true
}, {
title: () => $gettext('Domain'),
dataIndex: 'domain',
sorter: true,
pithy: true,
edit: {
type: input
},
search: true
}, {
title: () => $gettext('Auto Cert'),
dataIndex: 'auto_cert',
customRender: (args: customRender) => {
const template: any = []
const {text, column} = args
if (text === true || text > 0) {
template.push(<Badge status="success"/>)
template.push($gettext('Enabled'))
} else {
template.push(<Badge status="error"/>)
template.push($gettext('Disabled'))
}
return h('div', template)
},
sorter: true,
pithy: true
}, {
title: () => $gettext('SSL Certificate Path'),
dataIndex: 'ssl_certificate_path',
edit: {
type: input
},
display: false
}, {
title: () => $gettext('SSL Certificate Key Path'),
dataIndex: 'ssl_certificate_key_path',
edit: {
type: input
},
display: false
}, {
title: () => $gettext('Updated at'),
dataIndex: 'updated_at',
customRender: datetime,
sorter: true,
pithy: true
}, {
title: () => $gettext('Action'),
dataIndex: 'action'
}]
</script>
<template>
<std-curd :title="$gettext('Certification')" :api="cert" :columns="columns"
row-key="name"
/>
</template>
<style lang="less" scoped>
</style>

View file

@ -3,7 +3,6 @@ import gettext from '@/gettext'
const {$gettext} = gettext
import {Badge} from 'ant-design-vue'
import {h} from 'vue'
const configColumns = [{

View file

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style lang="less" scoped>
</style>

View file

@ -1 +1 @@
{"version":"1.7.0","build_id":62,"total_build":132}
{"version":"1.7.0","build_id":63,"total_build":133}

View file

@ -6,6 +6,7 @@ import (
"github.com/0xJacky/Nginx-UI/server/pkg/nginx"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/spf13/cast"
"log"
"net/http"
"strings"
@ -117,7 +118,8 @@ func IssueCert(c *gin.Context) {
}
err = certModel.Updates(&model.Cert{
SSLCertificatePath: sslCertificatePath,
SSLCertificatePath: sslCertificatePath,
SSLCertificateKeyPath: sslCertificateKeyPath,
})
if err != nil {
@ -137,3 +139,108 @@ func IssueCert(c *gin.Context) {
}
}
func GetCertList(c *gin.Context) {
certList := model.GetCertList(c.Query("name"), c.Query("domain"))
c.JSON(http.StatusOK, gin.H{
"data": certList,
})
}
func GetCert(c *gin.Context) {
certModel, err := model.FirstCertByID(cast.ToInt(c.Param("id")))
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, certModel)
}
func AddCert(c *gin.Context) {
var json struct {
Name string `json:"name" binding:"required"`
Domain string `json:"domain" binding:"required"`
SSLCertificatePath string `json:"ssl_certificate_path" binding:"required"`
SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
}
if !BindAndValid(c, &json) {
return
}
certModel, err := model.FirstOrCreateCert(json.Domain)
if err != nil {
ErrHandler(c, err)
return
}
err = certModel.Updates(&model.Cert{
Name: json.Name,
Domain: json.Domain,
SSLCertificatePath: json.SSLCertificatePath,
SSLCertificateKeyPath: json.SSLCertificateKeyPath,
})
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, nil)
}
func ModifyCert(c *gin.Context) {
id := cast.ToInt(c.Param("id"))
certModel, err := model.FirstCertByID(id)
var json struct {
Name string `json:"name" binding:"required"`
Domain string `json:"domain" binding:"required"`
SSLCertificatePath string `json:"ssl_certificate_path" binding:"required"`
SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required"`
}
if !BindAndValid(c, &json) {
return
}
if err != nil {
ErrHandler(c, err)
return
}
err = certModel.Updates(&model.Cert{
Name: json.Name,
Domain: json.Domain,
SSLCertificatePath: json.SSLCertificatePath,
SSLCertificateKeyPath: json.SSLCertificateKeyPath,
})
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, certModel)
}
func RemoveCert(c *gin.Context) {
id := cast.ToInt(c.Param("id"))
certModel, err := model.FirstCertByID(id)
if err != nil {
ErrHandler(c, err)
return
}
err = certModel.Remove()
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, nil)
}

View file

@ -1,365 +1,381 @@
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/nginx"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"path/filepath"
"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/nginx"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
func GetDomains(c *gin.Context) {
name := c.Query("name")
orderBy := c.Query("order_by")
sort := c.DefaultQuery("sort", "desc")
name := c.Query("name")
orderBy := c.Query("order_by")
sort := c.DefaultQuery("sort", "desc")
mySort := map[string]string{
"enabled": "bool",
"name": "string",
"modify": "time",
}
mySort := map[string]string{
"enabled": "bool",
"name": "string",
"modify": "time",
}
configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available"))
configFiles, err := os.ReadDir(nginx.GetNginxConfPath("sites-available"))
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
enabledConfigMap := make(map[string]bool)
for i := range enabledConfig {
enabledConfigMap[enabledConfig[i].Name()] = true
}
enabledConfigMap := make(map[string]bool)
for i := range enabledConfig {
enabledConfigMap[enabledConfig[i].Name()] = true
}
var configs []gin.H
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()],
})
}
}
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)
configs = config_list.Sort(orderBy, sort, mySort[orderBy], configs)
c.JSON(http.StatusOK, gin.H{
"data": 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 := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
enabled := true
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
enabled = false
}
enabled := true
if _, err := os.Stat(filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
enabled = false
}
config, err := nginx.ParseNgxConfig(path)
config, err := nginx.ParseNgxConfig(path)
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
certInfoMap := make(map[int]CertificateInfo)
var serverName string
for serverIdx, server := range config.Servers {
for _, directive := range server.Directives {
certInfoMap := make(map[int]CertificateInfo)
var serverName string
for serverIdx, server := range config.Servers {
for _, directive := range server.Directives {
if directive.Directive == "server_name" {
serverName = strings.ReplaceAll(directive.Params, " ", "_")
continue
}
if directive.Directive == "server_name" {
serverName = strings.ReplaceAll(directive.Params, " ", "_")
continue
}
if directive.Directive == "ssl_certificate" {
if directive.Directive == "ssl_certificate" {
pubKey, err := cert.GetCertInfo(directive.Params)
pubKey, err := cert.GetCertInfo(directive.Params)
if err != nil {
log.Println("Failed to get certificate information", err)
break
}
if err != nil {
log.Println("Failed to get certificate information", err)
break
}
certInfoMap[serverIdx] = CertificateInfo{
SubjectName: pubKey.Subject.CommonName,
IssuerName: pubKey.Issuer.CommonName,
NotAfter: pubKey.NotAfter,
NotBefore: pubKey.NotBefore,
}
certInfoMap[serverIdx] = CertificateInfo{
SubjectName: pubKey.Subject.CommonName,
IssuerName: pubKey.Issuer.CommonName,
NotAfter: pubKey.NotAfter,
NotBefore: pubKey.NotBefore,
}
break
}
}
}
break
}
}
}
_, err = model.FirstCert(serverName)
certModel, _ := model.FirstCert(serverName)
c.JSON(http.StatusOK, gin.H{
"enabled": enabled,
"name": name,
"config": config.BuildConfig(),
"tokenized": config,
"auto_cert": err == nil,
"cert_info": certInfoMap,
})
c.JSON(http.StatusOK, gin.H{
"enabled": enabled,
"name": name,
"config": config.BuildConfig(),
"tokenized": config,
"auto_cert": certModel.AutoCert == model.AutoCertEnabled,
"cert_info": certInfoMap,
})
}
func EditDomain(c *gin.Context) {
name := c.Param("name")
name := c.Param("name")
if name == "" {
c.JSON(http.StatusNotAcceptable, gin.H{
"message": "param name is empty",
})
return
}
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"`
}
var json struct {
Name string `json:"name" binding:"required"`
Content string `json:"content"`
}
if !BindAndValid(c, &json) {
return
}
if !BindAndValid(c, &json) {
return
}
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
path := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
err := os.WriteFile(path, []byte(json.Content), 0644)
if err != nil {
ErrHandler(c, err)
return
}
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
// rename the config file if needed
if name != json.Name {
newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name)
// recreate soft link
log.Println(enabledConfigFilePath)
if _, err = os.Stat(enabledConfigFilePath); err == nil {
log.Println(enabledConfigFilePath)
_ = os.Remove(enabledConfigFilePath)
enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), json.Name)
err = os.Symlink(newPath, enabledConfigFilePath)
err := os.WriteFile(path, []byte(json.Content), 0644)
if err != nil {
ErrHandler(c, err)
return
}
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
// rename the config file if needed
if name != json.Name {
newPath := filepath.Join(nginx.GetNginxConfPath("sites-available"), json.Name)
// recreate soft link
log.Println(enabledConfigFilePath)
if _, err = os.Stat(enabledConfigFilePath); err == nil {
log.Println(enabledConfigFilePath)
_ = os.Remove(enabledConfigFilePath)
enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("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)
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 = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
if _, err = os.Stat(enabledConfigFilePath); err == nil {
// Test nginx configuration
err = nginx.TestNginxConf()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
enabledConfigFilePath = filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
if _, err = os.Stat(enabledConfigFilePath); err == nil {
// Test nginx configuration
err = nginx.TestNginxConf()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
output := nginx.ReloadNginx()
output := nginx.ReloadNginx()
if output != "" && strings.Contains(output, "error") {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
}
if output != "" && strings.Contains(output, "error") {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
}
GetDomain(c)
GetDomain(c)
}
func EnableDomain(c *gin.Context) {
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
configFilePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), c.Param("name"))
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
_, err := os.Stat(configFilePath)
_, err := os.Stat(configFilePath)
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
err = os.Symlink(configFilePath, enabledConfigFilePath)
if _, err = os.Stat(enabledConfigFilePath); os.IsNotExist(err) {
err = os.Symlink(configFilePath, enabledConfigFilePath)
if err != nil {
ErrHandler(c, err)
return
}
}
if err != nil {
ErrHandler(c, err)
return
}
}
// Test nginx config, if not pass then rollback.
err = nginx.TestNginxConf()
if err != nil {
_ = os.Remove(enabledConfigFilePath)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
// Test nginx config, if not pass then rollback.
err = nginx.TestNginxConf()
if err != nil {
_ = os.Remove(enabledConfigFilePath)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
output := nginx.ReloadNginx()
output := nginx.ReloadNginx()
if output != "" && strings.Contains(output, "error") {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
if output != "" && strings.Contains(output, "error") {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func DisableDomain(c *gin.Context) {
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
enabledConfigFilePath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), c.Param("name"))
_, err := os.Stat(enabledConfigFilePath)
_, err := os.Stat(enabledConfigFilePath)
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
err = os.Remove(enabledConfigFilePath)
err = os.Remove(enabledConfigFilePath)
if err != nil {
ErrHandler(c, err)
return
}
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
}
// delete auto cert record
certModel := model.Cert{Domain: c.Param("name")}
err = certModel.Remove()
if err != nil {
ErrHandler(c, err)
return
}
output := nginx.ReloadNginx()
output := nginx.ReloadNginx()
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
if output != "" {
c.JSON(http.StatusInternalServerError, gin.H{
"message": output,
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func DeleteDomain(c *gin.Context) {
var err error
name := c.Param("name")
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
enabledPath := filepath.Join(nginx.GetNginxConfPath("sites-enabled"), name)
var err error
name := c.Param("name")
availablePath := filepath.Join(nginx.GetNginxConfPath("sites-available"), name)
enabledPath := filepath.Join(nginx.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(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
}
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()
certModel := model.Cert{Domain: name}
_ = certModel.Remove()
err = os.Remove(availablePath)
err = os.Remove(availablePath)
if err != nil {
ErrHandler(c, err)
return
}
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
c.JSON(http.StatusOK, gin.H{
"message": "ok",
})
}
func AddDomainToAutoCert(c *gin.Context) {
domain := c.Param("domain")
domain := c.Param("domain")
domain = strings.ReplaceAll(domain, " ", "_")
certModel, err := model.FirstOrCreateCert(domain)
certModel, err := model.FirstOrCreateCert(domain)
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, certModel)
if err != nil {
ErrHandler(c, err)
return
}
err = certModel.Updates(&model.Cert{
AutoCert: model.AutoCertEnabled,
})
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, certModel)
}
func RemoveDomainFromAutoCert(c *gin.Context) {
certModel := model.Cert{
Domain: c.Param("domain"),
}
err := certModel.Remove()
domain := c.Param("domain")
domain = strings.ReplaceAll(domain, " ", "_")
certModel := model.Cert{
Domain: domain,
}
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, nil)
err := certModel.Updates(&model.Cert{
AutoCert: model.AutoCertDisabled,
})
if err != nil {
ErrHandler(c, err)
return
}
c.JSON(http.StatusOK, nil)
}

View file

@ -6,10 +6,18 @@ import (
"path/filepath"
)
const (
AutoCertEnabled = 1
AutoCertDisabled = -1
)
type Cert struct {
Model
Domain string `json:"domain"`
SSLCertificatePath string `json:"ssl_certificate_path"`
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"`
}
func FirstCert(domain string) (c Cert, err error) {
@ -27,7 +35,7 @@ func FirstOrCreateCert(domain string) (c Cert, err error) {
func GetAutoCertList() (c []Cert) {
var t []Cert
db.Find(&t)
db.Where("auto_cert", AutoCertEnabled).Find(&t)
// check if this domain is enabled
enabledConfig, err := os.ReadDir(filepath.Join(nginx.GetNginxConfPath("sites-enabled")))
@ -50,6 +58,24 @@ func GetAutoCertList() (c []Cert) {
return
}
func GetCertList(name, domain string) (c []Cert) {
tx := db
if name != "" {
tx = tx.Where("name LIKE ? or domain LIKE ?", "%"+name+"%", "%"+name+"%")
}
if domain != "" {
tx = tx.Where("domain LIKE ?", "%"+domain+"%")
}
tx.Find(&c)
return
}
func FirstCertByID(id int) (c Cert, err error) {
err = db.First(&c, id).Error
return
}
func (c *Cert) Updates(n *Cert) error {
return db.Model(c).Updates(n).Error
}

View file

@ -77,10 +77,15 @@ func InitRouter() *gin.Engine {
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("cert/:domain", api.AddDomainToAutoCert)
g.POST("auto_cert/:domain", api.AddDomainToAutoCert)
// Delete domain from auto-renew cert list
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
g.DELETE("auto_cert/:domain", api.RemoveDomainFromAutoCert)
// pty
g.GET("pty", api.Pty)