mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-12 02:45:49 +02:00
embed frontend
This commit is contained in:
parent
882fe8c074
commit
d09f484790
86 changed files with 884 additions and 12326 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ database.db
|
||||||
tmp
|
tmp
|
||||||
node_modules
|
node_modules
|
||||||
app.ini
|
app.ini
|
||||||
|
dist
|
||||||
|
|
12
README.md
12
README.md
|
@ -74,16 +74,10 @@ server {
|
||||||
root /path/to/nginx-ui/frontend/dist;
|
root /path/to/nginx-ui/frontend/dist;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
# First attempt to serve request as file, then
|
|
||||||
# as directory, then fall back to displaying a 404.
|
|
||||||
index index.html;
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real_IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection upgrade;
|
proxy_set_header Connection upgrade;
|
||||||
|
|
57
api/auth.go
Normal file
57
api/auth.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginUser struct {
|
||||||
|
Name string `json:"name" binding:"required,max=255"`
|
||||||
|
Password string `json:"password" binding:"required,max=255"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Login(c *gin.Context) {
|
||||||
|
var user LoginUser
|
||||||
|
ok := BindAndValid(c, &user)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _ := model.GetUser(user.Name)
|
||||||
|
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"message": "用户名或密码错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := model.GenerateJWT(u.Name)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "ok",
|
||||||
|
"token": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logout(c *gin.Context) {
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token != "" {
|
||||||
|
err := model.DeleteToken(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusNoContent, nil)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/unknwon/com"
|
"github.com/unknwon/com"
|
||||||
"net/http"
|
"net/http"
|
|
@ -2,7 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"log"
|
"log"
|
|
@ -1,7 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
263
api/domain.go
Normal file
263
api/domain.go
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetDomains(c *gin.Context) {
|
||||||
|
orderBy := c.Query("order_by")
|
||||||
|
sort := c.DefaultQuery("sort", "desc")
|
||||||
|
|
||||||
|
mySort := map[string]string{
|
||||||
|
"enabled": "bool",
|
||||||
|
"name": "string",
|
||||||
|
"modify": "time",
|
||||||
|
}
|
||||||
|
|
||||||
|
configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
|
||||||
|
|
||||||
|
enabledConfigMap := make(map[string]bool)
|
||||||
|
for i := range enabledConfig {
|
||||||
|
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var configs []gin.H
|
||||||
|
|
||||||
|
for i := range configFiles {
|
||||||
|
file := configFiles[i]
|
||||||
|
if !file.IsDir() {
|
||||||
|
configs = append(configs, gin.H{
|
||||||
|
"name": file.Name(),
|
||||||
|
"size": file.Size(),
|
||||||
|
"modify": file.ModTime(),
|
||||||
|
"enabled": enabledConfigMap[file.Name()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"configs": configs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDomain(c *gin.Context) {
|
||||||
|
name := c.Param("name")
|
||||||
|
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||||
|
|
||||||
|
enabled := true
|
||||||
|
if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = model.FirstCert(name)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"enabled": enabled,
|
||||||
|
"name": name,
|
||||||
|
"config": string(content),
|
||||||
|
"auto_cert": err == nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditDomain(c *gin.Context) {
|
||||||
|
var err error
|
||||||
|
name := c.Param("name")
|
||||||
|
request := make(gin.H)
|
||||||
|
err = c.BindJSON(&request)
|
||||||
|
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||||
|
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||||
|
// 测试配置文件
|
||||||
|
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := tool.ReloadNginx()
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": output,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetDomain(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnableDomain(c *gin.Context) {
|
||||||
|
configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||||
|
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||||
|
|
||||||
|
_, err := os.Stat(configFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试配置文件,不通过则撤回启用
|
||||||
|
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(enabledConfigFilePath)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := tool.ReloadNginx()
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": output,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableDomain(c *gin.Context) {
|
||||||
|
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||||
|
|
||||||
|
_, err := os.Stat(enabledConfigFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(enabledConfigFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
output := tool.ReloadNginx()
|
||||||
|
|
||||||
|
if output != "" {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"message": output,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "ok",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteDomain(c *gin.Context) {
|
||||||
|
var err error
|
||||||
|
name := c.Param("name")
|
||||||
|
availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||||
|
enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||||
|
|
||||||
|
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"message": "site not found",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(enabledPath); err == nil {
|
||||||
|
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||||
|
"message": "site is enabled",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := model.Cert{Domain: name}
|
||||||
|
_ = cert.Remove()
|
||||||
|
|
||||||
|
err = os.Remove(availablePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "ok",
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDomainToAutoCert(c *gin.Context) {
|
||||||
|
domain := c.Param("domain")
|
||||||
|
cert, err := model.FirstOrCreateCert(domain)
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveDomainFromAutoCert(c *gin.Context) {
|
||||||
|
cert := model.Cert{
|
||||||
|
Domain: c.Param("domain"),
|
||||||
|
}
|
||||||
|
err := cert.Remove()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, nil)
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
37
api/template.go
Normal file
37
api/template.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTemplate(c *gin.Context) {
|
||||||
|
name := c.Param("name")
|
||||||
|
path := filepath.Join("template", name)
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
|
||||||
|
_content := string(content)
|
||||||
|
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
||||||
|
settings.ServerSettings.HTTPChallengePort)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{
|
||||||
|
"message": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ErrHandler(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "ok",
|
||||||
|
"template": _content,
|
||||||
|
})
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
BIN
frontend/dist/favicon.ico
vendored
BIN
frontend/dist/favicon.ico
vendored
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
BIN
frontend/dist/img/logo.9e691c6b.png
vendored
BIN
frontend/dist/img/logo.9e691c6b.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB |
11356
frontend/dist/img/remixicon.symbol.f09b1c74.svg
vendored
11356
frontend/dist/img/remixicon.symbol.f09b1c74.svg
vendored
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 877 KiB |
1
frontend/dist/index.html
vendored
1
frontend/dist/index.html
vendored
|
@ -1 +0,0 @@
|
||||||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width,initial-scale=1,user-scalable=0" name="viewport"><link href="/favicon.ico" rel="icon"><title>Nginx UI</title><link href="/js/chunk-00be396e.f6afc813.js" rel="prefetch"><link href="/js/chunk-0393876a.0e2e8183.js" rel="prefetch"><link href="/js/chunk-05148b16.66291bd9.js" rel="prefetch"><link href="/js/chunk-09f0acda.b788b1ae.js" rel="prefetch"><link href="/js/chunk-2881409a.1f853726.js" rel="prefetch"><link href="/js/chunk-2d0cf277.c260d8d5.js" rel="prefetch"><link href="/js/chunk-312c57da.53fa07de.js" rel="prefetch"><link href="/js/chunk-4216c952.f0073bdb.js" rel="prefetch"><link href="/js/chunk-46dcb584.5ee0f4ea.js" rel="prefetch"><link href="/js/chunk-4f82bf3d.8d3be338.js" rel="prefetch"><link href="/js/chunk-5573b71a.92b99af4.js" rel="prefetch"><link href="/js/chunk-5d4d188e.b0ffa164.js" rel="prefetch"><link href="/js/chunk-6a4ca29d.5593b7e1.js" rel="prefetch"><link href="/js/chunk-83d83096.72980dc3.js" rel="prefetch"><link href="/js/chunk-b508de6a.b421b1eb.js" rel="prefetch"><link href="/js/chunk-c8c0a686.85e5c7a1.js" rel="prefetch"><link href="/js/chunk-vendors.731e48fc.js" rel="modulepreload" as="script"><link href="/js/index.62a46eff.js" rel="modulepreload" as="script"></head><body><noscript><strong>We're sorry but Nginx UI doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script type="module" src="/js/chunk-vendors.731e48fc.js"></script><script type="module" src="/js/index.62a46eff.js"></script><script>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script><script src="/js/chunk-vendors-legacy.731e48fc.js" nomodule></script><script src="/js/index-legacy.ec1dfd27.js" nomodule></script></body></html>
|
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-00be396e.f6afc813.js
vendored
1
frontend/dist/js/chunk-00be396e.f6afc813.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);
|
|
1
frontend/dist/js/chunk-0393876a.0e2e8183.js
vendored
1
frontend/dist/js/chunk-0393876a.0e2e8183.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);
|
|
File diff suppressed because one or more lines are too long
3
frontend/dist/js/chunk-05148b16.66291bd9.js
vendored
3
frontend/dist/js/chunk-05148b16.66291bd9.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);
|
|
1
frontend/dist/js/chunk-09f0acda.b788b1ae.js
vendored
1
frontend/dist/js/chunk-09f0acda.b788b1ae.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);
|
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-2881409a.1f853726.js
vendored
1
frontend/dist/js/chunk-2881409a.1f853726.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);
|
|
1
frontend/dist/js/chunk-2d0cf277.c260d8d5.js
vendored
1
frontend/dist/js/chunk-2d0cf277.c260d8d5.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);
|
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-312c57da.53fa07de.js
vendored
1
frontend/dist/js/chunk-312c57da.53fa07de.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4216c952"],{3177:function(e,t,r){var n=r("6f0f");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var a=r("499e").default;a("4d98c476",n,!0,{sourceMap:!1,shadowMode:!1})},"6f0f":function(e,t,r){var n=r("24fb");t=n(!1),t.push([e.i,".container{display:flex;align-items:center;justify-content:center;height:100%}.container .login-form{max-width:400px;width:80%}.container .login-form .project-title{margin:50px}.container .login-form .project-title h1{font-size:50px;font-weight:100;text-align:center}.container .login-form .footer{padding:30px;text-align:center}",""]),e.exports=t},b2b2:function(e,t,r){"use strict";r("3177")},bf90:function(e,t,r){"use strict";r.r(t);var n=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"container"},[r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["name",{rules:[{required:!0,message:"请输入用户名"}]}],expression:"[\n 'name',\n { rules: [{ required: true, message: '请输入用户名' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"请输入密码"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: '请输入密码' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 登录 ")])],1)],1),r("div",{staticClass:"footer"},[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)])},a=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],o=r("1da1"),i=(r("96cf"),r("b0c0"),{name:"Login",data:function(){return{form:{},thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock||e.$router.push("/install")})),this.$store.state.user.token&&this.$router.push("/")},methods:{login:function(e){var t=this;return this.$api.auth.login(e.name,e.password).then(Object(o["a"])(regeneratorRuntime.mark((function e(){var r;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.next=2,t.$message.success("登录成功",1);case 2:return r=t.$route.query.next?t.$route.query.next:"/",e.next=5,t.$router.push(r);case 5:case"end":return e.stop()}}),e)})))).catch((function(e){var r;console.log(e),t.$message.error(null!==(r=e.message)&&void 0!==r?r:"服务器错误")}))},handleSubmit:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t,n){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(t){e.next=3;break}return e.next=3,r.login(n);case 3:r.loading=!1;case 4:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=i,c=(r("b2b2"),r("2877")),u=Object(c["a"])(s,n,a,!1,null,null,null);t["default"]=u.exports}}]);
|
|
1
frontend/dist/js/chunk-4216c952.f0073bdb.js
vendored
1
frontend/dist/js/chunk-4216c952.f0073bdb.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4216c952"],{3177:function(e,t,r){var n=r("6f0f");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var a=r("499e").default;a("4d98c476",n,!0,{sourceMap:!1,shadowMode:!1})},"6f0f":function(e,t,r){var n=r("24fb");t=n(!1),t.push([e.i,".container{display:flex;align-items:center;justify-content:center;height:100%}.container .login-form{max-width:400px;width:80%}.container .login-form .project-title{margin:50px}.container .login-form .project-title h1{font-size:50px;font-weight:100;text-align:center}.container .login-form .footer{padding:30px;text-align:center}",""]),e.exports=t},b2b2:function(e,t,r){"use strict";r("3177")},bf90:function(e,t,r){"use strict";r.r(t);var n=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"container"},[r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["name",{rules:[{required:!0,message:"请输入用户名"}]}],expression:"[\n 'name',\n { rules: [{ required: true, message: '请输入用户名' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"请输入密码"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: '请输入密码' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 登录 ")])],1)],1),r("div",{staticClass:"footer"},[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)])},a=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],o=r("1da1"),i=(r("96cf"),r("b0c0"),{name:"Login",data:function(){return{form:{},thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock||e.$router.push("/install")})),this.$store.state.user.token&&this.$router.push("/")},methods:{login:function(e){var t=this;return this.$api.auth.login(e.name,e.password).then(Object(o["a"])(regeneratorRuntime.mark((function e(){var r;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.next=2,t.$message.success("登录成功",1);case 2:return r=t.$route.query.next?t.$route.query.next:"/",e.next=5,t.$router.push(r);case 5:case"end":return e.stop()}}),e)})))).catch((function(e){var r;console.log(e),t.$message.error(null!==(r=e.message)&&void 0!==r?r:"服务器错误")}))},handleSubmit:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t,n){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(t){e.next=3;break}return e.next=3,r.login(n);case 3:r.loading=!1;case 4:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=i,c=(r("b2b2"),r("2877")),u=Object(c["a"])(s,n,a,!1,null,null,null);t["default"]=u.exports}}]);
|
|
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-46dcb584"],{"0125":function(e,t,r){var a=r("24fb");t=a(!1),t.push([e.i,".project-title{margin:50px}.project-title h1{font-size:50px;font-weight:100;text-align:center}.login-form{max-width:500px;margin:0 auto}footer{padding:30px;text-align:center}",""]),e.exports=t},"0f6e":function(e,t,r){"use strict";r("76f8")},"76f8":function(e,t,r){var a=r("0125");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[e.i,a,""]]),a.locals&&(e.exports=a.locals);var n=r("499e").default;n("447bb34c",a,!0,{sourceMap:!1,shadowMode:!1})},c756:function(e,t,r){"use strict";r.r(t);var a=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{staticClass:"login-form",attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["email",{rules:[{type:"email",message:"The input is not valid E-mail!"},{required:!0,message:"Please input your E-mail!"}]}],expression:"[\n 'email',\n { rules: [{\n type: 'email',\n message: 'The input is not valid E-mail!',\n },\n {\n required: true,\n message: 'Please input your E-mail!',\n },] },\n ]"}],attrs:{placeholder:"Email"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"mail"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["username",{rules:[{required:!0,message:"Please input your username!"}]}],expression:"[\n 'username',\n { rules: [{ required: true, message: 'Please input your username!' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"Please input your Password!"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: 'Please input your Password!' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 安装 ")])],1)],1),r("footer",[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)},n=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],i=r("1da1"),o=(r("96cf"),{name:"Login",data:function(){return{form:{},lock:!0,thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock&&e.$router.push("/login")}))},methods:{handleSubmit:function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t,a){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:t||r.$api.install.install_nginx_ui(a).then((function(){r.$router.push("/login")})),r.loading=!1;case 2:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=o,l=(r("0f6e"),r("2877")),u=Object(l["a"])(s,a,n,!1,null,null,null);t["default"]=u.exports}}]);
|
|
1
frontend/dist/js/chunk-46dcb584.5ee0f4ea.js
vendored
1
frontend/dist/js/chunk-46dcb584.5ee0f4ea.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-46dcb584"],{"0125":function(e,t,r){var a=r("24fb");t=a(!1),t.push([e.i,".project-title{margin:50px}.project-title h1{font-size:50px;font-weight:100;text-align:center}.login-form{max-width:500px;margin:0 auto}footer{padding:30px;text-align:center}",""]),e.exports=t},"0f6e":function(e,t,r){"use strict";r("76f8")},"76f8":function(e,t,r){var a=r("0125");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[e.i,a,""]]),a.locals&&(e.exports=a.locals);var n=r("499e").default;n("447bb34c",a,!0,{sourceMap:!1,shadowMode:!1})},c756:function(e,t,r){"use strict";r.r(t);var a=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{staticClass:"login-form",attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["email",{rules:[{type:"email",message:"The input is not valid E-mail!"},{required:!0,message:"Please input your E-mail!"}]}],expression:"[\n 'email',\n { rules: [{\n type: 'email',\n message: 'The input is not valid E-mail!',\n },\n {\n required: true,\n message: 'Please input your E-mail!',\n },] },\n ]"}],attrs:{placeholder:"Email"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"mail"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["username",{rules:[{required:!0,message:"Please input your username!"}]}],expression:"[\n 'username',\n { rules: [{ required: true, message: 'Please input your username!' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"Please input your Password!"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: 'Please input your Password!' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 安装 ")])],1)],1),r("footer",[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)},n=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],i=r("1da1"),o=(r("96cf"),{name:"Login",data:function(){return{form:{},lock:!0,thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock&&e.$router.push("/login")}))},methods:{handleSubmit:function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t,a){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:t||r.$api.install.install_nginx_ui(a).then((function(){r.$router.push("/login")})),r.loading=!1;case 2:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=o,l=(r("0f6e"),r("2877")),u=Object(l["a"])(s,a,n,!1,null,null,null);t["default"]=u.exports}}]);
|
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-4f82bf3d.8d3be338.js
vendored
1
frontend/dist/js/chunk-4f82bf3d.8d3be338.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-5573b71a.92b99af4.js
vendored
1
frontend/dist/js/chunk-5573b71a.92b99af4.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
frontend/dist/js/chunk-5d4d188e.b0ffa164.js
vendored
8
frontend/dist/js/chunk-5d4d188e.b0ffa164.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6a4ca29d"],{"06de":function(e,t,a){"use strict";a.r(t);var n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("a-card",{staticStyle:{"text-align":"center"}},[a("div",{staticClass:"logo"},[a("img",{attrs:{src:e.logo,alt:"logo"}})]),a("h2",[e._v("Nginx UI")]),a("p",[e._v("Yet another WebUI for Nginx")]),a("p",[e._v("Version: "+e._s(e.version)+" ("+e._s(e.build_id)+")")]),a("h3",[e._v("项目组")]),a("p",[e._v("Designer:"),a("a",{attrs:{href:"https://jackyu.cn/"}},[e._v("@0xJacky")])]),a("h3",[e._v("技术栈")]),a("p",[e._v("Go")]),a("p",[e._v("Gin")]),a("p",[e._v("Vue")]),a("p",[e._v("Websocket")]),a("h3",[e._v("开源协议")]),a("p",[e._v("GNU General Public License v2.0")]),a("p",[e._v("Copyright © 2020 - "+e._s(e.this_year)+" 0xJacky ")])])},r=[],o=a("1da1"),s=(a("96cf"),{name:"About",data:function(){var e,t=new Date;return{logo:a("4ffd"),this_year:t.getFullYear(),version:"1.1.0",build_id:null!==(e="19")&&void 0!==e?e:"开发模式",api_root:"/api"}},methods:{changeUserPower:function(e){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function a(){return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return a.next=2,t.$store.dispatch("update_mock_user",{power:e});case 2:return a.next=4,t.$api.user.info();case 4:return a.next=6,t.$message.success("修改成功");case 6:case"end":return a.stop()}}),a)})))()}}}),c=s,i=(a("a695"),a("2877")),u=Object(i["a"])(c,n,r,!1,null,"bb72cb78",null);t["default"]=u.exports},"0c2d":function(e,t,a){var n=a("24fb");t=n(!1),t.push([e.i,".logo img[data-v-bb72cb78]{max-width:120px}.egg[data-v-bb72cb78]{padding:10px 0}.ant-btn[data-v-bb72cb78]{margin:10px 10px 0 0}",""]),e.exports=t},"4ffd":function(e,t,a){e.exports=a.p+"img/logo.9e691c6b.png"},5683:function(e,t,a){var n=a("0c2d");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var r=a("499e").default;r("fc536a1c",n,!0,{sourceMap:!1,shadowMode:!1})},a695:function(e,t,a){"use strict";a("5683")}}]);
|
|
1
frontend/dist/js/chunk-6a4ca29d.5593b7e1.js
vendored
1
frontend/dist/js/chunk-6a4ca29d.5593b7e1.js
vendored
|
@ -1 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6a4ca29d"],{"06de":function(e,t,a){"use strict";a.r(t);var n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("a-card",{staticStyle:{"text-align":"center"}},[a("div",{staticClass:"logo"},[a("img",{attrs:{src:e.logo,alt:"logo"}})]),a("h2",[e._v("Nginx UI")]),a("p",[e._v("Yet another WebUI for Nginx")]),a("p",[e._v("Version: "+e._s(e.version)+" ("+e._s(e.build_id)+")")]),a("h3",[e._v("项目组")]),a("p",[e._v("Designer:"),a("a",{attrs:{href:"https://jackyu.cn/"}},[e._v("@0xJacky")])]),a("h3",[e._v("技术栈")]),a("p",[e._v("Go")]),a("p",[e._v("Gin")]),a("p",[e._v("Vue")]),a("p",[e._v("Websocket")]),a("h3",[e._v("开源协议")]),a("p",[e._v("GNU General Public License v2.0")]),a("p",[e._v("Copyright © 2020 - "+e._s(e.this_year)+" 0xJacky ")])])},r=[],o=a("1da1"),s=(a("96cf"),{name:"About",data:function(){var e,t=new Date;return{logo:a("4ffd"),this_year:t.getFullYear(),version:"1.1.0",build_id:null!==(e="19")&&void 0!==e?e:"开发模式",api_root:"/api"}},methods:{changeUserPower:function(e){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function a(){return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return a.next=2,t.$store.dispatch("update_mock_user",{power:e});case 2:return a.next=4,t.$api.user.info();case 4:return a.next=6,t.$message.success("修改成功");case 6:case"end":return a.stop()}}),a)})))()}}}),c=s,i=(a("a695"),a("2877")),u=Object(i["a"])(c,n,r,!1,null,"bb72cb78",null);t["default"]=u.exports},"0c2d":function(e,t,a){var n=a("24fb");t=n(!1),t.push([e.i,".logo img[data-v-bb72cb78]{max-width:120px}.egg[data-v-bb72cb78]{padding:10px 0}.ant-btn[data-v-bb72cb78]{margin:10px 10px 0 0}",""]),e.exports=t},"4ffd":function(e,t,a){e.exports=a.p+"img/logo.9e691c6b.png"},5683:function(e,t,a){var n=a("0c2d");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var r=a("499e").default;r("fc536a1c",n,!0,{sourceMap:!1,shadowMode:!1})},a695:function(e,t,a){"use strict";a("5683")}}]);
|
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-83d83096.72980dc3.js
vendored
1
frontend/dist/js/chunk-83d83096.72980dc3.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-b508de6a.b421b1eb.js
vendored
1
frontend/dist/js/chunk-b508de6a.b421b1eb.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-c8c0a686.85e5c7a1.js
vendored
1
frontend/dist/js/chunk-c8c0a686.85e5c7a1.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
39
frontend/dist/js/chunk-vendors.731e48fc.js
vendored
39
frontend/dist/js/chunk-vendors.731e48fc.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/js/index-legacy.ec1dfd27.js
vendored
1
frontend/dist/js/index-legacy.ec1dfd27.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/js/index.62a46eff.js
vendored
1
frontend/dist/js/index.62a46eff.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/version.json
vendored
1
frontend/dist/version.json
vendored
|
@ -1 +0,0 @@
|
||||||
{"version":"1.1.0","build_id":2,"total_build":19}
|
|
6
frontend/frontend.go
Normal file
6
frontend/frontend.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package frontend
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed dist
|
||||||
|
var DistFS embed.FS
|
|
@ -1 +1 @@
|
||||||
{"version":"1.1.0","build_id":2,"total_build":19}
|
{"version":"1.1.0","build_id":3,"total_build":20}
|
|
@ -1,13 +1,14 @@
|
||||||
module github.com/0xJacky/Nginx-UI/server
|
module github.com/0xJacky/Nginx-UI
|
||||||
|
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
|
github.com/gin-contrib/static v0.0.1
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.4
|
||||||
github.com/go-acme/lego/v4 v4.4.0
|
github.com/go-acme/lego/v4 v4.4.0
|
||||||
github.com/go-playground/locales v0.13.0 // indirect
|
github.com/go-playground/locales v0.13.0
|
||||||
github.com/go-playground/universal-translator v0.17.0
|
github.com/go-playground/universal-translator v0.17.0
|
||||||
github.com/go-playground/validator/v10 v10.4.1
|
github.com/go-playground/validator/v10 v10.4.1
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
|
@ -107,6 +107,9 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||||
|
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||||
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
||||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
|
github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
|
||||||
|
@ -128,6 +131,7 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
32
main.go
Normal file
32
main.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/0xJacky/Nginx-UI/router"
|
||||||
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var dataDir string
|
||||||
|
flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
settings.Init(dataDir)
|
||||||
|
model.Init()
|
||||||
|
|
||||||
|
r := router.InitRouter()
|
||||||
|
|
||||||
|
log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
|
||||||
|
|
||||||
|
go tool.AutoCert()
|
||||||
|
|
||||||
|
err := r.Run(":" + settings.ServerSettings.HttpPort)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
model/auth.go
Normal file
63
model/auth.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Model
|
||||||
|
|
||||||
|
Name string `json:"name"`
|
||||||
|
Password string `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTClaims struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
jwt.StandardClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(name string) (user Auth, err error) {
|
||||||
|
err = db.Where("name = ?", name).First(&user).Error
|
||||||
|
if err != nil {
|
||||||
|
return Auth{}, err
|
||||||
|
}
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteToken(token string) error {
|
||||||
|
return db.Where("token = ?", token).Delete(&AuthToken{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckToken(token string) int64 {
|
||||||
|
return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateJWT(name string) (string, error) {
|
||||||
|
claims := JWTClaims{
|
||||||
|
Name: name,
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Create(&AuthToken{
|
||||||
|
Token: signedToken,
|
||||||
|
}).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedToken, err
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
134
router/routers.go
Normal file
134
router/routers.go
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/0xJacky/Nginx-UI/api"
|
||||||
|
"github.com/0xJacky/Nginx-UI/frontend"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/gin-contrib/static"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func authRequired() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
|
||||||
|
token = string(tmp)
|
||||||
|
if token == "" {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"message": "auth fail",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n := model.CheckToken(token)
|
||||||
|
|
||||||
|
if n < 1 {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"message": "auth fail",
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type serverFileSystemType struct {
|
||||||
|
http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f serverFileSystemType) Exists(prefix string, path string) bool {
|
||||||
|
_, err := f.Open(filepath.Join(prefix, path))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
|
||||||
|
|
||||||
|
sub, err := fs.Sub(frontend.DistFS, filepath.Join("public", dir))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverFileSystem = serverFileSystemType{
|
||||||
|
http.FS(sub),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitRouter() *gin.Engine {
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.Logger())
|
||||||
|
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
r.Use(static.Serve("/", mustFS("")))
|
||||||
|
|
||||||
|
r.NoRoute(func(c *gin.Context) {
|
||||||
|
accept := c.Request.Header.Get("Accept")
|
||||||
|
if strings.Contains(accept, "text/html") {
|
||||||
|
file, _ := mustFS("").Open("index.html")
|
||||||
|
stat, _ := file.Stat()
|
||||||
|
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
||||||
|
bufio.NewReader(file), nil)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
g := r.Group("/api")
|
||||||
|
{
|
||||||
|
g.GET("install", api.InstallLockCheck)
|
||||||
|
g.POST("install", api.InstallNginxUI)
|
||||||
|
|
||||||
|
g.POST("/login", api.Login)
|
||||||
|
g.DELETE("/logout", api.Logout)
|
||||||
|
|
||||||
|
g := g.Group("/", authRequired())
|
||||||
|
{
|
||||||
|
g.GET("/analytic", api.Analytic)
|
||||||
|
|
||||||
|
g.GET("/users", api.GetUsers)
|
||||||
|
g.GET("/user/:id", api.GetUser)
|
||||||
|
g.POST("/user", api.AddUser)
|
||||||
|
g.POST("/user/:id", api.EditUser)
|
||||||
|
g.DELETE("/user/:id", api.DeleteUser)
|
||||||
|
|
||||||
|
g.GET("domains", api.GetDomains)
|
||||||
|
g.GET("domain/:name", api.GetDomain)
|
||||||
|
g.POST("domain/:name", api.EditDomain)
|
||||||
|
g.POST("domain/:name/enable", api.EnableDomain)
|
||||||
|
g.POST("domain/:name/disable", api.DisableDomain)
|
||||||
|
g.DELETE("domain/:name", api.DeleteDomain)
|
||||||
|
|
||||||
|
g.GET("configs", api.GetConfigs)
|
||||||
|
g.GET("config/:name", api.GetConfig)
|
||||||
|
g.POST("config", api.AddConfig)
|
||||||
|
g.POST("config/:name", api.EditConfig)
|
||||||
|
|
||||||
|
g.GET("backups", api.GetFileBackupList)
|
||||||
|
g.GET("backup/:id", api.GetFileBackup)
|
||||||
|
|
||||||
|
g.GET("template/:name", api.GetTemplate)
|
||||||
|
|
||||||
|
g.GET("cert/issue/:domain", api.IssueCert)
|
||||||
|
g.GET("cert/:domain/info", api.CertInfo)
|
||||||
|
|
||||||
|
// 添加域名到自动续期列表
|
||||||
|
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||||
|
// 从自动续期列表中删除域名
|
||||||
|
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
|
@ -1,57 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoginUser struct {
|
|
||||||
Name string `json:"name" binding:"required,max=255"`
|
|
||||||
Password string `json:"password" binding:"required,max=255"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Login(c *gin.Context) {
|
|
||||||
var user LoginUser
|
|
||||||
ok := BindAndValid(c, &user)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u, _ := model.GetUser(user.Name)
|
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
|
||||||
"message": "用户名或密码错误",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := model.GenerateJWT(u.Name)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "ok",
|
|
||||||
"token": token,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Logout(c *gin.Context) {
|
|
||||||
token := c.GetHeader("Authorization")
|
|
||||||
if token != "" {
|
|
||||||
err := model.DeleteToken(token)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusNoContent, nil)
|
|
||||||
}
|
|
|
@ -1,263 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetDomains(c *gin.Context) {
|
|
||||||
orderBy := c.Query("order_by")
|
|
||||||
sort := c.DefaultQuery("sort", "desc")
|
|
||||||
|
|
||||||
mySort := map[string]string{
|
|
||||||
"enabled": "bool",
|
|
||||||
"name": "string",
|
|
||||||
"modify": "time",
|
|
||||||
}
|
|
||||||
|
|
||||||
configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
|
|
||||||
|
|
||||||
enabledConfigMap := make(map[string]bool)
|
|
||||||
for i := range enabledConfig {
|
|
||||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var configs []gin.H
|
|
||||||
|
|
||||||
for i := range configFiles {
|
|
||||||
file := configFiles[i]
|
|
||||||
if !file.IsDir() {
|
|
||||||
configs = append(configs, gin.H{
|
|
||||||
"name": file.Name(),
|
|
||||||
"size": file.Size(),
|
|
||||||
"modify": file.ModTime(),
|
|
||||||
"enabled": enabledConfigMap[file.Name()],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"configs": configs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDomain(c *gin.Context) {
|
|
||||||
name := c.Param("name")
|
|
||||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
|
||||||
|
|
||||||
enabled := true
|
|
||||||
if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := ioutil.ReadFile(path)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = model.FirstCert(name)
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"enabled": enabled,
|
|
||||||
"name": name,
|
|
||||||
"config": string(content),
|
|
||||||
"auto_cert": err == nil,
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func EditDomain(c *gin.Context) {
|
|
||||||
var err error
|
|
||||||
name := c.Param("name")
|
|
||||||
request := make(gin.H)
|
|
||||||
err = c.BindJSON(&request)
|
|
||||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
|
||||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
|
||||||
// 测试配置文件
|
|
||||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output := tool.ReloadNginx()
|
|
||||||
|
|
||||||
if output != "" {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": output,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GetDomain(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EnableDomain(c *gin.Context) {
|
|
||||||
configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
|
|
||||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
|
||||||
|
|
||||||
_, err := os.Stat(configFilePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试配置文件,不通过则撤回启用
|
|
||||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
|
||||||
if err != nil {
|
|
||||||
_ = os.Remove(enabledConfigFilePath)
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output := tool.ReloadNginx()
|
|
||||||
|
|
||||||
if output != "" {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": output,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "ok",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisableDomain(c *gin.Context) {
|
|
||||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
|
||||||
|
|
||||||
_, err := os.Stat(enabledConfigFilePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(enabledConfigFilePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
output := tool.ReloadNginx()
|
|
||||||
|
|
||||||
if output != "" {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"message": output,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "ok",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteDomain(c *gin.Context) {
|
|
||||||
var err error
|
|
||||||
name := c.Param("name")
|
|
||||||
availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
|
||||||
enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
|
||||||
|
|
||||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"message": "site not found",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = os.Stat(enabledPath); err == nil {
|
|
||||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
|
||||||
"message": "site is enabled",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cert := model.Cert{Domain: name}
|
|
||||||
_ = cert.Remove()
|
|
||||||
|
|
||||||
err = os.Remove(availablePath)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "ok",
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddDomainToAutoCert(c *gin.Context) {
|
|
||||||
domain := c.Param("domain")
|
|
||||||
cert, err := model.FirstOrCreateCert(domain)
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, cert)
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveDomainFromAutoCert(c *gin.Context) {
|
|
||||||
cert := model.Cert{
|
|
||||||
Domain: c.Param("domain"),
|
|
||||||
}
|
|
||||||
err := cert.Remove()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.JSON(http.StatusOK, nil)
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetTemplate(c *gin.Context) {
|
|
||||||
name := c.Param("name")
|
|
||||||
path := filepath.Join("template", name)
|
|
||||||
content, err := ioutil.ReadFile(path)
|
|
||||||
|
|
||||||
_content := string(content)
|
|
||||||
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
|
||||||
settings.ServerSettings.HTTPChallengePort)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
c.JSON(http.StatusNotFound, gin.H{
|
|
||||||
"message": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ErrHandler(c, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "ok",
|
|
||||||
"template": _content,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/router"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var dataDir string
|
|
||||||
flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
settings.Init(dataDir)
|
|
||||||
model.Init()
|
|
||||||
|
|
||||||
r := router.InitRouter()
|
|
||||||
|
|
||||||
log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
|
|
||||||
|
|
||||||
go tool.AutoCert()
|
|
||||||
|
|
||||||
err := r.Run(":" + settings.ServerSettings.HttpPort)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Auth struct {
|
|
||||||
Model
|
|
||||||
|
|
||||||
Name string `json:"name"`
|
|
||||||
Password string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthToken struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWTClaims struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
jwt.StandardClaims
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUser(name string) (user Auth, err error){
|
|
||||||
err = db.Where("name = ?", name).First(&user).Error
|
|
||||||
if err != nil {
|
|
||||||
return Auth{}, err
|
|
||||||
}
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteToken(token string) error {
|
|
||||||
return db.Where("token = ?", token).Delete(&AuthToken{}).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckToken(token string) int64 {
|
|
||||||
return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
|
|
||||||
}
|
|
||||||
|
|
||||||
func GenerateJWT(name string) (string, error) {
|
|
||||||
claims := JWTClaims{
|
|
||||||
Name: name,
|
|
||||||
StandardClaims: jwt.StandardClaims{
|
|
||||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
||||||
signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.Create(&AuthToken{
|
|
||||||
Token: signedToken,
|
|
||||||
}).Error
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return signedToken, err
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
package router
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/api"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func authRequired() gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
token := c.GetHeader("Authorization")
|
|
||||||
if token == "" {
|
|
||||||
tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
|
|
||||||
token = string(tmp)
|
|
||||||
if token == "" {
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
|
||||||
"message": "auth fail",
|
|
||||||
})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n := model.CheckToken(token)
|
|
||||||
|
|
||||||
if n < 1 {
|
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
|
||||||
"message": "auth fail",
|
|
||||||
})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitRouter() *gin.Engine {
|
|
||||||
r := gin.New()
|
|
||||||
r.Use(gin.Logger())
|
|
||||||
|
|
||||||
r.Use(gin.Recovery())
|
|
||||||
|
|
||||||
r.GET("/", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "Hello World",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("install", api.InstallLockCheck)
|
|
||||||
r.POST("install", api.InstallNginxUI)
|
|
||||||
|
|
||||||
r.POST("/login", api.Login)
|
|
||||||
r.DELETE("/logout", api.Logout)
|
|
||||||
|
|
||||||
g := r.Group("/", authRequired())
|
|
||||||
{
|
|
||||||
r.GET("/analytic", api.Analytic)
|
|
||||||
|
|
||||||
g.GET("/users", api.GetUsers)
|
|
||||||
g.GET("/user/:id", api.GetUser)
|
|
||||||
g.POST("/user", api.AddUser)
|
|
||||||
g.POST("/user/:id", api.EditUser)
|
|
||||||
g.DELETE("/user/:id", api.DeleteUser)
|
|
||||||
|
|
||||||
g.GET("domains", api.GetDomains)
|
|
||||||
g.GET("domain/:name", api.GetDomain)
|
|
||||||
g.POST("domain/:name", api.EditDomain)
|
|
||||||
g.POST("domain/:name/enable", api.EnableDomain)
|
|
||||||
g.POST("domain/:name/disable", api.DisableDomain)
|
|
||||||
g.DELETE("domain/:name", api.DeleteDomain)
|
|
||||||
|
|
||||||
g.GET("configs", api.GetConfigs)
|
|
||||||
g.GET("config/:name", api.GetConfig)
|
|
||||||
g.POST("config", api.AddConfig)
|
|
||||||
g.POST("config/:name", api.EditConfig)
|
|
||||||
|
|
||||||
g.GET("backups", api.GetFileBackupList)
|
|
||||||
g.GET("backup/:id", api.GetFileBackup)
|
|
||||||
|
|
||||||
g.GET("template/:name", api.GetTemplate)
|
|
||||||
|
|
||||||
g.GET("cert/issue/:domain", api.IssueCert)
|
|
||||||
g.GET("cert/:domain/info", api.CertInfo)
|
|
||||||
|
|
||||||
// 添加域名到自动续期列表
|
|
||||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
|
||||||
// 从自动续期列表中删除域名
|
|
||||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAcme(t *testing.T) {
|
|
||||||
const acmePath = "/usr/local/acme.sh"
|
|
||||||
_, err := os.Stat(acmePath)
|
|
||||||
log.Println("[found] acme.sh ", acmePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
log.Println("[not found] acme.sh, installing...")
|
|
||||||
|
|
||||||
if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
|
|
||||||
_ = os.Mkdir("../tmp", 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := exec.Command("curl", "-o", "../tmp/acme.sh", "https://get.acme.sh").
|
|
||||||
CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n", out)
|
|
||||||
|
|
||||||
log.Println("[acme.sh] downloaded")
|
|
||||||
|
|
||||||
file, _ := ioutil.ReadFile("../tmp/acme.sh")
|
|
||||||
|
|
||||||
fileString := string(file)
|
|
||||||
fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
|
|
||||||
"https://ghproxy.com/https://raw.githubusercontent.com", -1)
|
|
||||||
|
|
||||||
_ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
|
|
||||||
|
|
||||||
out, err = exec.Command("bash", "../tmp/acme.sh",
|
|
||||||
"install",
|
|
||||||
"--log",
|
|
||||||
"--home", "/usr/local/acme.sh",
|
|
||||||
"--cert-home", tool.GetNginxConfPath("ssl")).
|
|
||||||
CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n", out)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCert(t *testing.T) {
|
|
||||||
out, err := exec.Command("bash", "/usr/local/acme.sh/acme.sh",
|
|
||||||
"--issue",
|
|
||||||
"-d", "test.ojbk.me",
|
|
||||||
"--nginx").CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n", out)
|
|
||||||
|
|
||||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println("[found]", "fullchain.cer")
|
|
||||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("[found]", "cert key")
|
|
||||||
|
|
||||||
log.Println("申请成功")
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
package tool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/model"
|
|
||||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
|
||||||
"github.com/go-acme/lego/v4/registration"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MyUser You'll need a user or account type that implements acme.User
|
|
||||||
type MyUser struct {
|
|
||||||
Email string
|
|
||||||
Registration *registration.Resource
|
|
||||||
key crypto.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *MyUser) GetEmail() string {
|
|
||||||
return u.Email
|
|
||||||
}
|
|
||||||
func (u MyUser) GetRegistration() *registration.Resource {
|
|
||||||
return u.Registration
|
|
||||||
}
|
|
||||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
|
||||||
return u.key
|
|
||||||
}
|
|
||||||
|
|
||||||
func AutoCert() {
|
|
||||||
for {
|
|
||||||
log.Println("[AutoCert] Start")
|
|
||||||
autoCertList := model.GetAutoCertList()
|
|
||||||
for i := range autoCertList {
|
|
||||||
domain := autoCertList[i].Domain
|
|
||||||
key := GetCertInfo(domain)
|
|
||||||
// 未到一个月
|
|
||||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 过一个月了
|
|
||||||
err := IssueCert(domain)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(1 * time.Hour)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCertInfo(domain string) (key *x509.Certificate) {
|
|
||||||
ts := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{Transport: ts}
|
|
||||||
|
|
||||||
response, err := client.Get("https://" + domain)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func(Body io.ReadCloser) {
|
|
||||||
err = Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}(response.Body)
|
|
||||||
|
|
||||||
key = response.TLS.PeerCertificates[0]
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func IssueCert(domain string) error {
|
|
||||||
// Create a user. New accounts need an email and private key to start.
|
|
||||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
myUser := MyUser{
|
|
||||||
Email: settings.ServerSettings.Email,
|
|
||||||
key: privateKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
config := lego.NewConfig(&myUser)
|
|
||||||
|
|
||||||
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
||||||
config.Certificate.KeyType = certcrypto.RSA2048
|
|
||||||
|
|
||||||
// A client facilitates communication with the CA server.
|
|
||||||
client, err := lego.NewClient(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = client.Challenge.SetHTTP01Provider(
|
|
||||||
http01.NewProviderServer("",
|
|
||||||
settings.ServerSettings.HTTPChallengePort,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// New users will need to register
|
|
||||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
myUser.Registration = reg
|
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
|
||||||
Domains: []string{domain},
|
|
||||||
Bundle: true,
|
|
||||||
}
|
|
||||||
certificates, err := client.Certificate.Obtain(request)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
saveDir := GetNginxConfPath("ssl/" + domain)
|
|
||||||
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
|
||||||
err = os.Mkdir(saveDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("fail to create", saveDir)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
|
||||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
|
||||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
|
||||||
certificates.Certificate, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
|
||||||
certificates.PrivateKey, 0644)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadNginx()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
59
test/acme_test.go
Normal file
59
test/acme_test.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAcme(t *testing.T) {
|
||||||
|
const acmePath = "/usr/local/acme.sh"
|
||||||
|
_, err := os.Stat(acmePath)
|
||||||
|
log.Println("[found] acme.sh ", acmePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Println("[not found] acme.sh, installing...")
|
||||||
|
|
||||||
|
if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
|
||||||
|
_ = os.Mkdir("../tmp", 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := exec.Command("curl", "-o", "../tmp/acme.sh", "https://get.acme.sh").
|
||||||
|
CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
|
||||||
|
log.Println("[acme.sh] downloaded")
|
||||||
|
|
||||||
|
file, _ := ioutil.ReadFile("../tmp/acme.sh")
|
||||||
|
|
||||||
|
fileString := string(file)
|
||||||
|
fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
|
||||||
|
"https://ghproxy.com/https://raw.githubusercontent.com", -1)
|
||||||
|
|
||||||
|
_ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
|
||||||
|
|
||||||
|
out, err = exec.Command("bash", "../tmp/acme.sh",
|
||||||
|
"install",
|
||||||
|
"--log",
|
||||||
|
"--home", "/usr/local/acme.sh",
|
||||||
|
"--cert-home", tool.GetNginxConfPath("ssl")).
|
||||||
|
CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
test/cert_test.go
Normal file
40
test/cert_test.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/0xJacky/Nginx-UI/tool"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCert(t *testing.T) {
|
||||||
|
out, err := exec.Command("bash", "/usr/local/acme.sh/acme.sh",
|
||||||
|
"--issue",
|
||||||
|
"-d", "test.ojbk.me",
|
||||||
|
"--nginx").CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", out)
|
||||||
|
|
||||||
|
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("[found]", "fullchain.cer")
|
||||||
|
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[found]", "cert key")
|
||||||
|
|
||||||
|
log.Println("申请成功")
|
||||||
|
}
|
169
tool/cert.go
Normal file
169
tool/cert.go
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
package tool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"github.com/0xJacky/Nginx-UI/model"
|
||||||
|
"github.com/0xJacky/Nginx-UI/settings"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
"github.com/go-acme/lego/v4/registration"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MyUser You'll need a user or account type that implements acme.User
|
||||||
|
type MyUser struct {
|
||||||
|
Email string
|
||||||
|
Registration *registration.Resource
|
||||||
|
key crypto.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *MyUser) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
func (u MyUser) GetRegistration() *registration.Resource {
|
||||||
|
return u.Registration
|
||||||
|
}
|
||||||
|
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||||
|
return u.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func AutoCert() {
|
||||||
|
for {
|
||||||
|
log.Println("[AutoCert] Start")
|
||||||
|
autoCertList := model.GetAutoCertList()
|
||||||
|
for i := range autoCertList {
|
||||||
|
domain := autoCertList[i].Domain
|
||||||
|
key := GetCertInfo(domain)
|
||||||
|
// 未到一个月
|
||||||
|
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 过一个月了
|
||||||
|
err := IssueCert(domain)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCertInfo(domain string) (key *x509.Certificate) {
|
||||||
|
ts := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{Transport: ts}
|
||||||
|
|
||||||
|
response, err := client.Get("https://" + domain)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err = Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(response.Body)
|
||||||
|
|
||||||
|
key = response.TLS.PeerCertificates[0]
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func IssueCert(domain string) error {
|
||||||
|
// Create a user. New accounts need an email and private key to start.
|
||||||
|
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
myUser := MyUser{
|
||||||
|
Email: settings.ServerSettings.Email,
|
||||||
|
key: privateKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
config := lego.NewConfig(&myUser)
|
||||||
|
|
||||||
|
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
config.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
|
// A client facilitates communication with the CA server.
|
||||||
|
client, err := lego.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.Challenge.SetHTTP01Provider(
|
||||||
|
http01.NewProviderServer("",
|
||||||
|
settings.ServerSettings.HTTPChallengePort,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// New users will need to register
|
||||||
|
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
myUser.Registration = reg
|
||||||
|
|
||||||
|
request := certificate.ObtainRequest{
|
||||||
|
Domains: []string{domain},
|
||||||
|
Bundle: true,
|
||||||
|
}
|
||||||
|
certificates, err := client.Certificate.Obtain(request)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
saveDir := GetNginxConfPath("ssl/" + domain)
|
||||||
|
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
||||||
|
err = os.Mkdir(saveDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("fail to create", saveDir)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||||
|
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||||
|
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||||
|
certificates.Certificate, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||||
|
certificates.PrivateKey, 0644)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ReloadNginx()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue